diff --git a/rogueviz/ascending-descending.cpp b/rogueviz/ascending-descending.cpp new file mode 100644 index 00000000..f1b7c716 --- /dev/null +++ b/rogueviz/ascending-descending.cpp @@ -0,0 +1,293 @@ +#include "rogueviz.h" + +namespace hr { + +namespace ply { + +bool in, animated; + +using namespace rogueviz::objmodels; + +hyperpoint A = point31(9.7, -38.1, 10.116); +hyperpoint B = point31(17.3, -38.1, 10.95); +hyperpoint C = point31(17.3, -46.1, 11.75); +hyperpoint D = point31(12.95, -46.08, 12.28); +hyperpoint E = point31(12.86, -44.53, 12.4); + +ld radius = (12.86 - 11.11) / 2; + +transmatrix nilform; + +map > cache; + +bool debugnil = false; +bool usecache = true; + +pair nilize(hyperpoint h) { + + hyperpoint hc = h; + for(int i=0; i<4; i++) hc[i] = floor(h[i] * 1000 + .5); + if(usecache && cache.count(hc)) return cache[hc]; + + hyperpoint at = A; + + hyperpoint result = C0; + + auto move_coord_full = [&] (hyperpoint tgt) { + for(int i=0; i<100; i++) { + result = nisot::translate(result) * nilform * ((tgt - at) / 100 + C0); + } + at = tgt; + }; + + auto move_coord = [&] (int c, hyperpoint upto) { + ld part = (h[c] - at[c]) / (upto[c] - at[c]); + if(part > 1) part = 1; + move_coord_full(at + part * (upto-at)); + }; + + int cat = 0; + + if(h[1] > -39) { + move_coord(0, B); + cat = 1; + goto finish; + } + move_coord_full(B); + if(h[0] > 17.2) { + move_coord(1, C); + cat = 2; + goto finish; + } + move_coord_full(C); + if(h[1] < -45.5) { + move_coord(0, D); + cat = 3; + goto finish; + } + cat = 4; + move_coord_full(D); + move_coord(1, E); + finish: + + move_coord_full(h); + return cache[hc] = {cat, result}; + } + +ld vperiod; + +pair trace_path(ld v) { + + ld vorig = v; + + hyperpoint lctr = A; + ld angle = 0; + + ld arclen = radius * M_PI/2; + + auto change_angle = [&] (ld x) { + if(v == 0) return; + else if(v >= arclen) v -= arclen, angle += 1; + else angle += v / arclen, v = 0; + }; + + auto shift_to = [&] (hyperpoint h, bool last = false) { + ld seglen = hypot_d(3, h - lctr); + if(v == 0) return; + else if(v >= seglen && !last) v -= seglen, lctr = h; + else lctr = lerp(lctr, h, v / seglen), v = 0; + }; + + change_angle(1); + shift_to(B); + change_angle(2); + shift_to(C); + change_angle(3); + shift_to(D); + change_angle(4); + shift_to(E, true); + + angle *= M_PI/2; + + if(v > 0) vperiod = vorig - v; + + return { lctr + point3(radius * -cos(angle), radius * sin(angle), 0), point3(-sin(angle), -cos(angle), 0) }; + } + +transmatrix scaleby(ld x) { + transmatrix res = Id; + for(int i=0; i route(1000), forwards(1000), overroute(1000); + +hyperpoint interpolate_at(vector& v, ld x) { + while(x > 1000) x -= 1000; + return lerp(v[x], v[(int(x)+1) % 1000], frac(x)); + } + +ld advance = 2; +ld over = 1.5; +ld over2 = 1; + +void make_routes() { + for(int i=0; i<1000; i++) { + ld v = vperiod * i / 1000; + route[i] = nilize(trace_path(v).first + point3(0,0,over)).second; + ld vb = v + advance; + if(vb > vperiod) vb -= vperiod; + forwards[i] = nilize(trace_path(vb).first).second; + overroute[i] = nilize(trace_path(v).first + point3(0,0,over+over2)).second; + } + + auto smoothen = [&] (vector& v) { + for(int it=0; it<100; it++) { + vector smoother; + for(int i=0; i<1000; i++) { + hyperpoint& a = v[i]; + hyperpoint& b = v[(i+1) % 1000]; + hyperpoint& c = v[(i+2) % 1000]; + smoother.push_back((a + b + c) / 3); + } + v = smoother; + } + }; + + smoothen(route); + smoothen(forwards); + smoothen(overroute); + } + +bool prepared; + +void prepare_nilform() { + if(prepared) return; + prepared = true; + hyperpoint axis = (E - A); + transmatrix rotator = Id; + rotator[2] = axis; + println(hlog, " = ", (rotator[2] | rotator[2])); + rotator[2] /= sqrt(rotator[2] | rotator[2]); + rotator[1] -= (rotator[1] | rotator[2]) * rotator[2]; + rotator[1] /= sqrt(rotator[1] | rotator[1]); + rotator[0] -= (rotator[0] | rotator[2]) * rotator[2]; + rotator[0] -= (rotator[0] | rotator[1]) * rotator[1]; + rotator[0] /= sqrt(rotator[0] | rotator[0]); + println(hlog, "rotator = ", kz(rotator)); + rotator = inverse(transpose(rotator)); + println(hlog, "rotator = ", kz(rotator)); + + ld minscale = 0.5; // positive + ld maxscale = 1.5; // negative + ld scale; + + for(int it=0; it<100; it++) { + scale = (minscale + maxscale) / 2; + nilform = rotator * scaleby(scale); + cache.clear(); + hyperpoint nE = nilize(E).second; + if(nE[2] < 0) maxscale = scale; + else minscale = scale; + } + + println(hlog, "scale = ", scale); + println(hlog, nilize(E).second); + + vperiod = radius * 2 * M_PI + hypot_d(3, B-A) + hypot_d(3, C-B) + hypot_d(3, D-C) + hypot_d(3, E-D); + println(hlog, "vperiod = ", vperiod); + + make_routes(); + } + +model staircase("rogueviz/nil/", "aco.obj", nilize); + +bool draw_ply() { + + if(!in) return false; + + if(nil) prepare_nilform(); + + staircase.render(ggmatrix(currentmap->gamestart())); + + return false; + } + +void show() { + cmode = sm::SIDE | sm::MAYDARK; + gamescreen(0); + dialog::init(XLAT("Ascending & Descending"), 0xFFFFFFFF, 150, 0); + + dialog::addSelItem("advance", fts(advance), 'a'); + dialog::add_action([]() { + dialog::editNumber(advance, 0, 100, 1, 1, "advance", ""); + dialog::reaction = make_routes; + }); + + dialog::addSelItem("over", fts(over), 'o'); + dialog::add_action([]() { + dialog::editNumber(over, 0, 100, 1, 1, "over", ""); + dialog::reaction = make_routes; + }); + + dialog::addSelItem("over2", fts(over2), 'p'); + dialog::add_action([]() { + dialog::editNumber(over2, 0, 100, 1, 1, "over2", ""); + dialog::reaction = make_routes; + }); + + dialog::addBoolItem_action("animated", animated, 'a'); + + dialog::addBack(); + dialog::display(); + } + +void o_key(o_funcs& v) { + if(in) v.push_back(named_dialog("Ascending & Descending", show)); + } + +auto plyhook = addHook(hooks_frame, 100, draw_ply) + ++ addHook(anims::hooks_anim, 100, [] { + if(!in || !animated) return; + usecache = false; + ld t = ticks * 1. / anims::period; + t = t - floor(t); + t *= 1000; + + centerover = currentmap->gamestart(); + set_view( + interpolate_at(route, t), + interpolate_at(forwards, t), + interpolate_at(overroute, t) + ); + + anims::moved(); + }) + ++ addHook(hooks_o_key, 80, o_key) + +#if CAP_COMMANDLINE ++ addHook(hooks_args, 100, [] { + using namespace arg; + + if(0) ; + else if(argis("-asd")) { + in = true; + } + else if(argis("-asd-prec")) { + shift(); prec = argi(); + } + else if(argis("-asd-anim")) { + in = true; + animated = true; + } + else return 1; + return 0; + }) +#endif + ; + +} +} diff --git a/rogueviz/backhead.cpp b/rogueviz/backhead.cpp new file mode 100644 index 00000000..e0fed8a7 --- /dev/null +++ b/rogueviz/backhead.cpp @@ -0,0 +1,189 @@ +#include "rogueviz.h" + +/* + +used for https://youtu.be/KxjnibOkuLs + +you need the models from https://renderpeople.com/free-3d-people/ (and convert them to the Wavefront OBJ format) + +-geo 120c -canvas 1 -noplayer camspd=.1 + +*/ + +namespace hr { + +namespace backhead { + +int view_model_id = 0; +int camera_model = 1; + +using namespace rogueviz::objmodels; + +map models; + +tf_result person_tf(hyperpoint h) { return {0, direct_exp(h*5)}; } + +ld smoothen(ld x) { return x*x*(3-2*x); } + +ld cm = 0.08 / 160; + +bool use_camera; + +transmatrix eyematrix(int id) { + transmatrix S = Id; + + int i = 0; + if(id == 1) for(ld val: {-0.997647,0.00073308,-0.0685486,7.63928e-05,0.0147183,-0.971198,-0.224516,0.0784166,-0.0669523,-0.224937,0.972022,0.00970247,-0.000429702,0.0785862,0.00820562,0.996873}) + S[0][i++] = val; + if(id == 0) for(ld val: {-0.976869,0.119464,-0.17719,-0.00766071,-0.0534002,-0.935386,-0.339797,0.0820953,-0.207062,-0.322171,0.923596,0.0173391,0.000492418,0.0835894,0.0105615,0.996444}) + S[0][i++] = val; + + return S; + } + +ld back = 0; + +bool do_anim = false; + +int tf; + +vector captions = { + "as seen by another person", + "changing the view...", + "L/R", + "downward", + "upwards", + "back" + }; + +EX bool ourStats() { + + /* + if(tf < 0 || tf >= 5) { println(hlog, "tf=", tf); tf = gmod(tf, 5); } + + println(hlog, "displaying tf = ", tf); + + displayfr(10, 10 + 7 * vid.fsize, 2, vid.fsize * 7, captions[tf], 0xFFFFFF, 0); + + nohelp = true; + nomenukey = true; + clearMessages(); + + glflush(); + + println(hlog, "done?"); + */ + + return true; + } + +void face_animation() { + + if(!do_anim) return; + + ld t = ticks / anims::period; + + t = t - floor(t); + + t *= 2; + view_model_id = (t >= 1 ? 0 : 1); + camera_model = 1 - view_model_id; + if(t>=1) t -= 1; + + t *= 4.5; + + if(t < 1) { + if(t < .7) t /= .7; + else t = (t-.7) / .3 + 1; + } + else + t++; + + // t *= 5; + + tf = t; + t -= tf; + + View = Id; + + ld up = view_model_id == 0 ? 82 : 90; + ld down = view_model_id == 1 ? 65 : 70; + + up -= 15; down -= 15; + + tie(up, down) = make_pair(-down, -up); + + if(tf == 0) { + View = cspin(0, 2, 360 * degree * smoothen(t)) * View; + View = zpush(-0.1) * View; + View = cspin(0, 2, M_PI) * View; + back = 0; + } + else if(tf == 1) { + View = zpush(-0.1 * (1-smoothen(t))) * View; + if(t > .9) + back = -0.1 * smoothen(10*t-9); + View = cspin(0, 2, M_PI) * View; + } + else if(tf == 2) { + View = cspin(0, 2, 75*degree*sin(2*M_PI*smoothen(t))) * View; + } + else if(tf == 3) { + View = cspin(1, 2, up*degree*smoothen(t)) * View; + } + else if(tf == 4) { + View = cspin(1, 2, degree*(up-(up+down)*smoothen(t))) * View; + } + else if(tf == 5) { + View = cspin(1, 2, degree*-down*(1-smoothen(t*2))) * View; + } + + use_camera = tf <= 1; + + sightranges[geometry] = use_camera ? 30 : 100; + if(tf == 5) sightranges[geometry] = 100 - 70 * smoothen(t*2); + + anims::moved(); + + hide_hud = false; + } + +bool draw_ply() { + + if(models.empty()) { + models[0] = model("rogueviz/models/", "dennis.obj", person_tf); + models[1] = model("rogueviz/models/", "mei.obj", person_tf); + } + + shiftmatrix Zero = ggmatrix(currentmap->gamestart()); + + Zero = Zero * eyematrix(view_model_id); + println(hlog, Zero); + + models[view_model_id].render(Zero); + + if(use_camera) models[camera_model].render(shiftless(eyematrix(camera_model) * zpush(back))); + + return false; + } + +auto plyhook = addHook(hooks_frame, 100, draw_ply) + + addHook(anims::hooks_anim, 100, face_animation) ++ addHook(hooks_args, 100, [] { + using namespace arg; + + if(0) ; + else if(argis("-head-swap")) { + swap(view_model_id, camera_model); + } + else if(argis("-head-anim")) { + do_anim = !do_anim; + } + else return 1; + return 0; + }) + + addHook(hooks_prestats, 100, ourStats) + ; + +} +} \ No newline at end of file diff --git a/rogueviz/hypcity.cpp b/rogueviz/hypcity.cpp new file mode 100644 index 00000000..afc9d1c3 --- /dev/null +++ b/rogueviz/hypcity.cpp @@ -0,0 +1,94 @@ +#include "rogueviz.h" + +// 'hyperbolic city' demo +// download the model from https://sketchfab.com/3d-models/night-city-p2-82637933a7cb4fafadb0e2a79415c438 as rogueviz/models/emilejohansson_p2.obj + +// see the results posted here: + +// https://twitter.com/ZenoRogue/status/1375750351391981570 +// -noplayer -geo 4x5 -gp 1 1 -unrectified -switch-fpp -canvas 303030 camera=0 depth=0 -sr 3 -PM 0 -alpha 1 -zoom .95 + +// https://twitter.com/ZenoRogue/status/1375748835046215682 +// -noplayer -geo nil -canvas 303030 -back 44e4 -sight3 3 + +// https://twitter.com/ZenoRogue/status/1375754422752575488 +// add -PM 0 -alpha 1 + +namespace hr { + +using namespace rogueviz::objmodels; + +model city("rogueviz/models/", "emilejohansson_p2.obj"); + +hyperpoint low, high; + +void prepare_tf() { + if(!city.models.empty()) return; + + prec = 40; + + for(int i=0; i<4; i++) low[i] = 100, high[i] = -100; + + cgi.require_basics(); + hyperpoint corner = get_corner_position(cwt.at, 0); + + ld t = abs(corner[0] / corner[3]); + + city.tf = [=] (hyperpoint h) -> pair { + swap(h[1], h[2]); + h[2] = -h[2]; + h[2] += 0.063; + h[0] -= 0.063; + h[1] += 0.063; + h *= 6; + // h[0] -= .5; + // h[1] += .5; + + for(int i=0; i<4; i++) + low[i] = min(low[i], h[i]), + high[i] = max(high[i], h[i]); + + if(hyperbolic) { + + hyperpoint hx; + hx[0] = h[0] * t * 2; + hx[1] = h[1] * t * 2; + hx[2] = 0; + hx[3] = 1; + hx = spin(45 * degree) * hx; + normalize(hx); + hx = zshift(hx, h[2]*(t*7)); + + return {0, hx}; + } + + if(nil) { + swap(h[1], h[2]); + h *= 0.5 / 0.378; + h[3] = 1; + return {0, h}; + } + + return {0, h}; + }; + println(hlog, "low = ", low); + println(hlog, "high = ", high); + } + +bool draw_city_at(cell *c, const shiftmatrix& V) { + prepare_tf(); + + if(nil) { + auto co = nilv::get_coord(c->master); + if(co[1]) return false; + } + + if(c == cwt.at || true) + city.render(V); + + return false; + } + +auto hypcity_ah = addHook(hooks_drawcell, 100, draw_city_at); + +} diff --git a/rogueviz/objmodels.cpp b/rogueviz/objmodels.cpp new file mode 100644 index 00000000..a63a9cc5 --- /dev/null +++ b/rogueviz/objmodels.cpp @@ -0,0 +1,243 @@ +/* a library for loading 3D models */ + +#include "rogueviz.h" + +namespace rogueviz { + +namespace objmodels { + +bool scan(fhstream& hs, char& c) { return fscanf(hs.f, "%c", &c) == 1; } + +char peek(fhstream& fs) { + char g = fgetc(fs.f); + ungetc(g, fs.f); + return g; + } + +void model::load_obj(model_type& objects) { + fhstream fs(path+fname, "rt"); + + if(!fs.f) + throw hr_exception("failed to open model file: " + path + fname); + + vector vertices; + vector normals; + vector tvertices; + + while(!feof(fs.f)) { + string s; + scan(fs, s); + if(s == "#") { scanline(fs); } + else if(s == "mtllib") { + string mtllib; + scan(fs, mtllib); + fhstream fsm(path+mtllib, "rt"); + if(!fsm.f) + throw hr_exception("failed to open mtllib: " + mtllib); + color_t nextcol = 0xFFFFFFFF; + string mtlname, texname = ""; + auto emit_material = [&] { + if(texname != "") { + texture::texture_data tdata; + materials[mtlname] = tdata; + auto& mat = materials[mtlname]; + mat.twidth = mat.theight = 0; mat.stretched = true; + println(hlog, "texname: ", texname); + mat.readtexture(path+texname); + mat.loadTextureGL(); + println(hlog, "texture ID: ", mat.textureid); + } + colors[mtlname] = nextcol; + println(hlog, "color of ", mtlname, " is ", nextcol); + }; + while(!feof(fsm.f)) { + string s; + scan(fsm, s); + if(s == "#") { scanline(fsm); } + if(s == "Kd") { + ld a, b, c; + scan(fsm, a, b, c); + part(nextcol, 1) = a * 319.99; + part(nextcol, 2) = b * 319.99; + part(nextcol, 3) = c * 319.99; + } + if(s == "newmtl") { + emit_material(); + nextcol = 0xFFFFFFFF; + texname = ""; + mtlname = scanline(fsm); + } + if(s == "map_Kd") { + scan(fsm, texname); + } + } + emit_material(); + } + else if(s == "o" || s == "g") { + next_object: + object *co = nullptr; + bool textured = false; + string oname = scanline(fs); + println(hlog, "reading object: ", oname); + while(true) { + if(feof(fs.f)) { + if(co) cgi.finishshape(); + if(co) println(hlog, "vertices = ", co->sh.e-co->sh.s, " tvertices = ", isize(co->tv.tvertices)); + break; + } + scan(fs, s); + if(s == "#") { + scanline(fs); + } + else if(s == "o" || s == "g") { + if(co) cgi.finishshape(); + if(co) println(hlog, "vertices = ", co->sh.e-co->sh.s, " tvertices = ", isize(co->tv.tvertices)); + goto next_object; + } + else if(s == "v") { + hyperpoint h = C0; + scan(fs, h[0], h[1], h[2]); // assume all + h[0] /= 100; + h[1] /= 100; + h[2] /= 100; + vertices.push_back(h); + } + else if(s == "vt") { + ld u, v; + scan(fs, u, v); + tvertices.push_back(point3(u, 1-v, 0)); + } + else if(s == "vn") { + hyperpoint hn = C0; + scan(fs, hn[0], hn[1], hn[2]); + normals.push_back(hn); + } + else if(s == "s") { + scan(fs, s); + } + else if(s == "usemtl") { + if(co) cgi.finishshape(); + if(co) println(hlog, "vertices = ", co->sh.e-co->sh.s, " tvertices = ", isize(co->tv.tvertices)); + string mtlname = scanline(fs); + co = nullptr; + if(mtlname.find("Layer_Layer0") != string::npos) continue; + objects.push_back(make_shared()); + co = &*objects.back(); + cgi.bshape(co->sh, PPR::WALL); + cgi.last->flags |= POLY_TRIANGLES; + cgi.last->texture_offset = 0; + if(materials.count(mtlname)) { + textured = true; + cgi.last->tinf = &co->tv; + co->tv.texture_id = materials[mtlname].textureid; + println(hlog, "using texture_id : ", co->tv.texture_id); + co->color = 0xFFFFFFFF; + } + else { + textured = false; + cgi.last->tinf = &co->tv; + co->tv.texture_id = floor_textures->renderedTexture; + if(colors.count(mtlname)) + co->color = colors[mtlname]; + else + co->color = 0xFFFFFFFF; + } + println(hlog, "set textured to ", textured); + } + else if(s == "f") { + struct vertexinfo { int f, t, n; }; + array vis; + vector hys; + vector tot; + char bar; + for(int i=0; i<3; i++) { + vis[i].f = vis[i].t = vis[i].n = 1; + scan(fs, vis[i].f); + if(peek(fs) == '/') { + scan(fs, bar); + if(peek(fs) != '/') scan(fs, vis[i].t); + } + if(peek(fs) == '/') { + scan(fs, bar); + scan(fs, vis[i].n); + } + + vis[i].f--; vis[i].t--; vis[i].n--; + if(vis[i].f < 0 || vis[i].f >= isize(vertices)) + throw hr_exception("illegal ID"); + hys.push_back(vertices[vis[i].f]); + tot.push_back(textured ? tvertices[vis[i].t] : point3(0,0,0)); + } + if(!co) continue; + + hyperpoint norm = (hys[1] - hys[0]) ^ (hys[2] - hys[0]); + norm /= hypot_d(3, norm); + ld y = .5 + (.2 * norm[0] + .16 * norm[1] + .14 * norm[2]); + glvertex shade = glhr::makevertex(0, y, 0); + glvertex shadecol = glhr::makevertex(y, y, y); + + auto n0 = tf(hys[0]); + auto n1 = tf(hys[1]); + auto n2 = tf(hys[2]); + auto mi = min(n0.first, min(n1.first, n2.first)); + auto ma = max(n0.first, max(n1.first, n2.first)); + if(ma - mi > 1) continue; + + int parts = sd(hys); + auto tri = [&] (int a, int b) { + cgi.hpcpush(tf(hys[0] + (hys[1] - hys[0]) * a / parts + (hys[2] - hys[0]) * b / parts).second); + // cgi.hpcpush(tf(tot[0] + (tot[1] - tot[0]) * a / parts + (tot[2] - tot[0]) * b / parts).second); + if(textured) { + co->tv.tvertices.push_back(glhr::pointtogl(tot[0] + (tot[1] - tot[0]) * a / parts + (tot[2] - tot[0]) * b / parts)); + co->tv.colors.push_back(shadecol); + } + else { + co->tv.tvertices.push_back(shade); + } + }; + + for(int a=0; ash, obj->color); + } + } + +} +} diff --git a/rogueviz/rogueviz.h b/rogueviz/rogueviz.h index a67d0f1e..30a64b5a 100644 --- a/rogueviz/rogueviz.h +++ b/rogueviz/rogueviz.h @@ -181,6 +181,55 @@ function roguevizslide_action(char c, const T& t, const U& act) colorpair perturb(colorpair cp); void queuedisk(const shiftmatrix& V, const colorpair& cp, bool legend, const string* info, int i); +/* 3D models */ + +namespace objmodels { + + using tf_result = pair; + + using transformer = std::function; + using subdivider = std::function&)>; + + inline int prec = 10; + + struct object { + hpcshape sh; + basic_textureinfo tv; + color_t color; + }; + + using model_type = vector>; + + struct model { + + string path, fname; + transformer tf; + subdivider sd; + + model(string path = "", string fn = "", + transformer tf = [] (hyperpoint h) { + return tf_result{0, direct_exp(h)}; + }, + subdivider sd = [] (vector& 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)); + } + ) : path(path), fname(fn), tf(tf), sd(sd) {} + + map materials; + map colors; + + map models; + + /* private */ + void load_obj(model_type& objects); + + void render(const shiftmatrix& V); + }; + + } + } #endif