1
0
mirror of https://github.com/zenorogue/hyperrogue.git synced 2024-12-24 01:00:25 +00:00

rogueviz:: added objmodels.cpp and some demos using it

This commit is contained in:
Zeno Rogue 2021-03-30 20:59:22 +02:00
parent ccc5e3bf9b
commit 30b79de1f5
5 changed files with 868 additions and 0 deletions

View File

@ -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<hyperpoint, pair<int, hyperpoint> > cache;
bool debugnil = false;
bool usecache = true;
pair<int, hyperpoint> 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<hyperpoint, hyperpoint> 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<LDIM; i++)
res[i][i] = x;
return res;
}
vector<hyperpoint> route(1000), forwards(1000), overroute(1000);
hyperpoint interpolate_at(vector<hyperpoint>& 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<hyperpoint>& v) {
for(int it=0; it<100; it++) {
vector<hyperpoint> 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
;
}
}

189
rogueviz/backhead.cpp Normal file
View File

@ -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<int, model> 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<string> 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)
;
}
}

94
rogueviz/hypcity.cpp Normal file
View File

@ -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<int, hyperpoint> {
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);
}

243
rogueviz/objmodels.cpp Normal file
View File

@ -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<hyperpoint> vertices;
vector<hyperpoint> normals;
vector<hyperpoint> 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<object>());
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<vertexinfo, 3> vis;
vector<hyperpoint> hys;
vector<hyperpoint> 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; a<parts; a++)
for(int b=0; b<parts-a; b++) {
tri(a, b);
tri(a+1, b);
tri(a, b+1);
if(a+b < parts-1) {
tri(a, b+1);
tri(a+1, b);
tri(a+1, b+1);
}
}
}
else if(s == "l") {
int a, b;
scan(fs, a, b);
/* ignore */
}
else if(s == "") { }
else
throw hr_exception("unknown command: " + s);
}
}
else
throw("unknown command: " + s);
}
println(hlog, "reading finished");
cgi.extra_vertices();
}
void model::render(const shiftmatrix& V) {
auto& objs = models[cgi_string()];
if(objs.empty()) load_obj(objs);
for(auto& obj: objs) {
queuepoly(V, obj->sh, obj->color);
}
}
}
}

View File

@ -181,6 +181,55 @@ function<void(presmode)> 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<int, hyperpoint>;
using transformer = std::function<tf_result(hyperpoint)>;
using subdivider = std::function<int(vector<hyperpoint>&)>;
inline int prec = 10;
struct object {
hpcshape sh;
basic_textureinfo tv;
color_t color;
};
using model_type = vector<shared_ptr<object>>;
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<hyperpoint>& 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<string, texture::texture_data> materials;
map<string, color_t> colors;
map<string, model_type> models;
/* private */
void load_obj(model_type& objects);
void render(const shiftmatrix& V);
};
}
}
#endif