From: John Tsiombikas Date: Mon, 22 Jan 2018 12:45:15 +0000 (-0800) Subject: Merge branch 'master' of goat:git/laserbrain_demo X-Git-Url: http://git.mutantstargoat.com/user/nuclear/?p=laserbrain_demo;a=commitdiff_plain;h=794a378d5c8e07c815814324da8142be137406d0;hp=1acecbdaaccffe30ae7d564e1e01fd834c8618c9 Merge branch 'master' of goat:git/laserbrain_demo --- diff --git a/Makefile b/Makefile index 00e0559..b7e244b 100644 --- a/Makefile +++ b/Makefile @@ -18,11 +18,11 @@ incpath = -Isrc -Isrc/machine -I/usr/local/include `pkg-config --cflags sdl2` warn = -pedantic -Wall -CFLAGS = $(warn) $(opt) $(dbg) $(incpath) -CXXFLAGS = -std=c++11 $(warn) $(opt) $(dbg) $(incpath) +CFLAGS = $(warn) $(opt) $(dbg) $(incpath) -fopenmp +CXXFLAGS = -std=c++11 $(warn) $(opt) $(dbg) $(incpath) -fopenmp LDFLAGS = $(libpath) -ldrawtext $(libgl_$(sys)) $(libal_$(sys)) -lm -lgmath -lvmath \ -limago -lresman -lpthread -lassimp -ltreestore -lgoatvr \ - `pkg-config --libs sdl2 freetype2` -lpng -ljpeg -lz -lvorbisfile + `pkg-config --libs sdl2 freetype2` -lpng -ljpeg -lz -lvorbisfile -lgomp sys = $(shell uname -s | sed 's/MINGW.*/mingw/') libgl_Linux = -lGL -lGLU -lGLEW diff --git a/sdr/dfont.p.glsl b/sdr/dfont.p.glsl new file mode 100644 index 0000000..dad3080 --- /dev/null +++ b/sdr/dfont.p.glsl @@ -0,0 +1,12 @@ +uniform sampler2D tex; + +void main() +{ + const float softness = 0.008; + + float alpha = texture2D(tex, gl_TexCoord[0].st).a; + float mask = smoothstep(0.5 - softness, 0.5 + softness, alpha); + + gl_FragColor.rgb = gl_Color.rgb; + gl_FragColor.a = mask; +} diff --git a/sdr/dfont.v.glsl b/sdr/dfont.v.glsl new file mode 100644 index 0000000..7aa01e4 --- /dev/null +++ b/sdr/dfont.v.glsl @@ -0,0 +1,6 @@ +void main() +{ + gl_Position = ftransform(); + gl_TexCoord[0] = gl_MultiTexCoord0; + gl_FrontColor = gl_Color; +} diff --git a/src/app.cc b/src/app.cc index d9d44c0..0ac331b 100644 --- a/src/app.cc +++ b/src/app.cc @@ -1,4 +1,5 @@ #include +#include #include #include #include "app.h" @@ -19,6 +20,8 @@ #include "exman.h" #include "blob_exhibit.h" #include "dbg_gui.h" +#include "geomdraw.h" +#include "ui_exhibit.h" #define NEAR_CLIP 5.0 #define FAR_CLIP 10000.0 @@ -26,6 +29,7 @@ static void draw_scene(); static void toggle_flight(); static void calc_framerate(); +static Ray calc_pick_ray(int x, int y); long time_msec; int win_width, win_height; @@ -38,6 +42,8 @@ SceneSet sceneman; unsigned int sdr_ltmap, sdr_ltmap_notex; +int fpexcept_enabled; + static Avatar avatar; static float cam_dist = 0.0; @@ -66,14 +72,34 @@ static unsigned int sdr_post_gamma; static long prev_msec; static ExhibitManager *exman; -static BlobExhibit *blobs; static bool show_blobs; +ExSelection exsel_active, exsel_hover; +ExSelection exsel_grab_left, exsel_grab_right; +#define exsel_grab_mouse exsel_grab_right +static ExhibitSlot exslot_left, exslot_right; +#define exslot_mouse exslot_right + static Renderer *rend; +static Ray last_pick_ray; + bool app_init(int argc, char **argv) { + set_log_file("demo.log"); + + char *env = getenv("FPEXCEPT"); + if(env && atoi(env)) { + info_log("enabling floating point exceptions\n"); + fpexcept_enabled = 1; + enable_fpexcept(); + } + + if(init_opengl() == -1) { + return false; + } + if(!init_options(argc, argv, "demo.conf")) { return false; } @@ -110,7 +136,6 @@ bool app_init(int argc, char **argv) glEnable(GL_MULTISAMPLE); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); - glEnable(GL_LIGHTING); glEnable(GL_NORMALIZE); if(!init_debug_gui()) { @@ -122,8 +147,6 @@ bool app_init(int argc, char **argv) float ambient[] = {0.0, 0.0, 0.0, 0.0}; glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient); - glClearColor(1, 1, 1, 1); - init_audio(); if(!init_vrhands()) { @@ -141,18 +164,16 @@ bool app_init(int argc, char **argv) avatar.body_rot = rad_to_deg(acos(dot(dir, Vec3(0, 0, 1)))); exman = new ExhibitManager; + /* if(!exman->load(mscn, "data/exhibits")) { //return false; } - - blobs = new BlobExhibit; - blobs->node = new SceneNode; - blobs->init(); - blobs->node->set_position(Vec3(-680, 160, -100)); - blobs->node->set_scaling(Vec3(28, 28, 28)); - blobs->node->update(0); - - exman->add(blobs); + */ + if(!exui_init()) { + error_log("failed to initialize exhibit ui system\n"); + return false; + } + exui_setnode(&exslot_left.node); if(!(sdr_ltmap_notex = create_program_load("sdr/lightmap.v.glsl", "sdr/lightmap-notex.p.glsl"))) { return false; @@ -200,6 +221,11 @@ void app_cleanup() delete rend; + exui_shutdown(); + + /* this must be destroyed before the scene graph to detach exhibit nodes + * before the scene tries to delete them recursively + */ delete exman; texman.clear(); @@ -233,6 +259,7 @@ static void update(float dt) mscn->update(dt); exman->update(dt); + exui_update(dt); float speed = walk_speed * dt; Vec3 dir; @@ -312,7 +339,7 @@ static void update(float dt) floor_y = avatar.pos.y - user_eye_height; } - // TODO move to avatar + // TODO move to the avatar system // calculate mouselook view matrix mouse_view_matrix = Mat4::identity; mouse_view_matrix.pre_translate(0, 0, -cam_dist); @@ -322,22 +349,28 @@ static void update(float dt) mouse_view_matrix.pre_rotate_y(deg_to_rad(avatar.body_rot)); mouse_view_matrix.pre_translate(-avatar.pos.x, -avatar.pos.y, -avatar.pos.z); + // check if an exhibit is hovered-over by mouse or 6dof (only if we don't have one grabbed) + if(!exsel_grab_mouse) { + // XXX note: using previous view/proj matrix lattency shouldn't be an issue but + // make sure state-creep doesn't get us + // XXX also this mouse-picking probably should only be active in non-VR mode + Ray ray = calc_pick_ray(prev_mx, prev_my); + exsel_hover = exman->select(ray); + } + // update hand-tracking if(have_handtracking) { update_vrhands(&avatar); + } else { + // TODO do this properly + // set the position of the left hand at a suitable position for the exhibit UI + dir = rotate(Vec3(-0.46, -0.1, -1), Vec3(0, 1, 0), deg_to_rad(-avatar.body_rot)); + exslot_left.node.set_position(avatar.pos + dir * 30); // magic: distance in front } -} -static void set_light(int idx, const Vec3 &pos, const Vec3 &color) -{ - unsigned int lt = GL_LIGHT0 + idx; - float posv[] = { pos.x, pos.y, pos.z, 1 }; - float colv[] = { color.x, color.y, color.z, 1 }; - - glEnable(lt); - glLightfv(lt, GL_POSITION, posv); - glLightfv(lt, GL_DIFFUSE, colv); - glLightfv(lt, GL_SPECULAR, colv); + if(!exslot_right.empty()) exslot_right.node.update(dt); + // always update the left slot, because it's the anchor point of the exhibit ui + exslot_left.node.update(dt); } void app_display() @@ -352,6 +385,8 @@ void app_display() ImGui::ShowTestWindow(); } + glClearColor(1, 1, 1, 1); + if(opt.vr) { // VR mode goatvr_draw_start(); @@ -419,16 +454,8 @@ void app_display() static void draw_scene() { - static const Vec3 lpos[] = { Vec3(-50, 75, 100), Vec3(100, 0, 30), Vec3(-10, -10, 60) }; - set_light(0, lpos[0], Vec3(1.0, 0.8, 0.7) * 0.8); - set_light(1, lpos[1], Vec3(0.6, 0.7, 1.0) * 0.6); - set_light(2, lpos[2], Vec3(0.8, 1.0, 0.8) * 0.3); - rend->draw(); - - if(show_blobs) { - blobs->draw(); - } + exman->draw(); /* if(have_handtracking) { @@ -462,11 +489,15 @@ static void draw_scene() } */ + if(debug_gui && dbg_sel_node) { + AABox bvol = dbg_sel_node->get_bounds(); + draw_geom_object(&bvol); + } + if(show_walk_mesh && mscn->walk_mesh) { glPushAttrib(GL_ENABLE_BIT); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE); - glDisable(GL_LIGHTING); glEnable(GL_POLYGON_OFFSET_FILL); glUseProgram(0); @@ -482,6 +513,8 @@ static void draw_scene() glPopAttrib(); } + exui_draw(); + print_text(Vec2(9 * win_width / 10, 20), Vec3(1, 1, 0), "fps: %.1f", framerate); draw_ui(); } @@ -580,6 +613,26 @@ void app_keyboard(int key, bool pressed) goatvr_recenter(); show_message("VR recenter\n"); break; + + case 'x': + exman->load(mscn, "data/exhibits"); + break; + + case KEY_UP: + exui_scroll(-1); + break; + + case KEY_DOWN: + exui_scroll(1); + break; + + case KEY_LEFT: + exui_change_tab(-1); + break; + + case KEY_RIGHT: + exui_change_tab(1); + break; } } @@ -590,6 +643,8 @@ void app_keyboard(int key, bool pressed) void app_mouse_button(int bn, bool pressed, int x, int y) { + static int press_x, press_y; + if(debug_gui) { debug_gui_mbutton(bn, pressed, x, y); return; // ignore mouse events while GUI is visible @@ -598,6 +653,70 @@ void app_mouse_button(int bn, bool pressed, int x, int y) prev_mx = x; prev_my = y; bnstate[bn] = pressed; + + if(bn == 0) { + ExSelection sel; + Ray ray = calc_pick_ray(x, y); + sel = exman->select(ray); + + if(pressed) { + if(sel && (app_get_modifiers() & MOD_CTRL)) { + exsel_grab_mouse = sel; + Vec3 pos = sel.ex->node->get_position(); + debug_log("grabbing... (%g %g %g)\n", pos.x, pos.y, pos.z); + exslot_mouse.node.set_position(pos); + exslot_mouse.node.set_rotation(sel.ex->node->get_rotation()); + exslot_mouse.attach_exhibit(sel.ex, EXSLOT_ATTACH_TRANSIENT); + if(exsel_active) { + exsel_active = ExSelection::null; // cancel active on grab + } + } + press_x = x; + press_y = y; + + } else { + if(exsel_grab_mouse) { + // cancel grab on mouse release + Exhibit *ex = exsel_grab_mouse.ex; + Vec3 pos = exslot_mouse.node.get_position(); + + debug_log("releasing at %g %g %g ...\n", pos.x, pos.y, pos.z); + + exslot_mouse.detach_exhibit(); + + ExhibitSlot *slot = exman->nearest_empty_slot(pos, 100); + if(!slot) { + debug_log("no empty slot nearby\n"); + if(ex->prev_slot && ex->prev_slot->empty()) { + slot = ex->prev_slot; + debug_log("using previous slot"); + } + } + + if(slot) { + slot->attach_exhibit(ex); + } else { + // nowhere to put it, so stash it for later + exslot_mouse.detach_exhibit(); + exman->stash_exhibit(ex); + debug_log("no slots available, stashing\n"); + } + + exsel_grab_mouse = ExSelection::null; + } + + if(abs(press_x - x) < 5 && abs(press_y - y) < 5) { + exsel_active = sel; // select or deselect active exhibit + if(sel) { + debug_log("selecting...\n"); + } else { + debug_log("deselecting...\n"); + } + } + + press_x = press_y = INT_MIN; + } + } } static inline void mouse_look(float dx, float dy) @@ -630,11 +749,15 @@ void app_mouse_motion(int x, int y) if(!dx && !dy) return; - if(bnstate[0]) { - mouse_look(dx, dy); + if(exsel_grab_mouse) { + Vec3 pos = exslot_mouse.node.get_node_position(); + Vec3 dir = transpose(view_matrix.upper3x3()) * Vec3(dx * 1.0, dy * -1.0, 0); + + exslot_mouse.node.set_position(pos + dir); } + if(bnstate[2]) { - mouse_zoom(dx, dy); + mouse_look(dx, dy); } } @@ -717,7 +840,7 @@ static void toggle_flight() static void calc_framerate() { - static int ncalc; + //static int ncalc; static int nframes; static long prev_upd; @@ -735,3 +858,12 @@ static void calc_framerate() ++nframes; } } + +static Ray calc_pick_ray(int x, int y) +{ + float nx = (float)x / (float)win_width; + float ny = (float)(win_height - y) / (float)win_height; + + last_pick_ray = mouse_pick_ray(nx, ny, view_matrix, proj_matrix); + return last_pick_ray; +} diff --git a/src/app.h b/src/app.h index 83680ec..686c7e0 100644 --- a/src/app.h +++ b/src/app.h @@ -3,6 +3,7 @@ #include "texture.h" #include "scene.h" +#include "exhibit.h" extern long time_msec; extern int win_width, win_height; @@ -15,6 +16,12 @@ extern SceneSet sceneman; extern unsigned int sdr_ltmap, sdr_ltmap_notex; +extern ExSelection exsel_active; // active (info/interact) exhibit +extern ExSelection exsel_grab_left, exsel_grab_right; // grabbed on each hand +extern ExSelection exsel_hover; // hover + +extern int fpexcept_enabled; // int so that C modules may fwd-delcare and use it + enum { MOD_SHIFT = 1, MOD_ALT = 2, diff --git a/src/avatar.h b/src/avatar.h index ca3ea8c..6c3aa41 100644 --- a/src/avatar.h +++ b/src/avatar.h @@ -1,6 +1,8 @@ #ifndef AVATAR_H_ #define AVATAR_H_ +// TODO incomplete + #include /* when head-tracking is available, head_tilt is ignored, and the diff --git a/src/blob_exhibit.cc b/src/blob_exhibit.cc index 5e9f213..cd29e96 100644 --- a/src/blob_exhibit.cc +++ b/src/blob_exhibit.cc @@ -1,6 +1,7 @@ #include "blob_exhibit.h" #include "blobs/metasurf.h" #include "app.h" +#include struct Metaball { Vec3 pos; @@ -22,21 +23,19 @@ static Metaball def_mball_data[] = { }; struct BlobPriv { + AABox vol; metasurface *msurf; Metaball mballs[NUM_MBALLS]; Texture *tex; }; -static void vertex(struct metasurface *ms, float x, float y, float z); -static float eval(struct metasurface *ms, float x, float y, float z); - - BlobExhibit::BlobExhibit() { priv = new BlobPriv; for(int i=0; imballs[i] = def_mball_data[i]; } + priv->vol = AABox(Vec3(-3.5, -3.5, -3.5), Vec3(3.5, 3.5, 3.5)); } BlobExhibit::~BlobExhibit() @@ -50,14 +49,14 @@ bool BlobExhibit::init() if(!(priv->msurf = msurf_create())) { return false; } - msurf_set_user_data(priv->msurf, priv); msurf_set_threshold(priv->msurf, 8); msurf_set_inside(priv->msurf, MSURF_GREATER); - msurf_set_bounds(priv->msurf, -3.5, 3.5, -3.5, 3.5, -3.5, 3.5); - msurf_eval_func(priv->msurf, eval); - msurf_vertex_func(priv->msurf, vertex); + msurf_set_bounds(priv->msurf, priv->vol.min.x, priv->vol.min.y, priv->vol.min.z, + priv->vol.max.x, priv->vol.max.y, priv->vol.max.z); + msurf_enable(priv->msurf, MSURF_NORMALIZE); priv->tex = texman.get_texture("data/sphmap.jpg"); + return true; } @@ -73,24 +72,67 @@ void BlobExhibit::update(float dt) { double sec = time_msec / 1000.0; + float xmin, xmax, ymin, ymax, zmin, zmax; + int xres, yres, zres; + + if(!msurf_voxels(priv->msurf)) { + return; + } + + msurf_get_bounds(priv->msurf, &xmin, &ymin, &zmin, &xmax, &ymax, &zmax); + msurf_get_resolution(priv->msurf, &xres, &yres, &zres); + + float xstep = (xmax - xmin) / xres; + float ystep = (ymax - ymin) / yres; + float zstep = (zmax - zmin) / zres; + for(int i=0; imballs[i].speed + priv->mballs[i].phase_offset, M_PI * 2.0); priv->mballs[i].pos.x = cos(t) * priv->mballs[i].path_scale.x + priv->mballs[i].path_offset.x; priv->mballs[i].pos.y = sin(t) * priv->mballs[i].path_scale.y + priv->mballs[i].path_offset.y; priv->mballs[i].pos.z = -cos(t) * priv->mballs[i].path_scale.z + priv->mballs[i].path_offset.z; } + + float max_energy = 0.0f; + +#pragma omp parallel for + for(int i=0; imsurf, i); + + for(int j=0; jmballs[n].pos.x; + float dy = y - priv->mballs[n].pos.y; + float dz = z - priv->mballs[n].pos.z; + float dsq = dx * dx + dy * dy + dz * dz; + + sum += priv->mballs[n].energy / dsq; + } + *voxptr++ = sum; + if(sum > max_energy) max_energy = sum; + } + } + } + + msurf_polygonize(priv->msurf); } void BlobExhibit::draw() const { - pre_draw(); - glPushAttrib(GL_ENABLE_BIT); glUseProgram(0); glDisable(GL_LIGHTING); glEnable(GL_TEXTURE_2D); + glEnable(GL_NORMALIZE); priv->tex->bind(); glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); @@ -98,50 +140,65 @@ void BlobExhibit::draw() const glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); + if(node) { + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glMultMatrixf(node->get_matrix()[0]); + } + glMatrixMode(GL_TEXTURE); glLoadIdentity(); glScalef(1, -1, 1); - glFrontFace(GL_CW); - glBegin(GL_TRIANGLES); - glColor3f(1, 1, 1); - msurf_polygonize(priv->msurf); - glEnd(); - glFrontFace(GL_CCW); + int nverts = msurf_vertex_count(priv->msurf); + float *varr = msurf_vertices(priv->msurf); + float *narr = msurf_normals(priv->msurf); - glLoadIdentity(); - glMatrixMode(GL_MODELVIEW); + glColor3f(1, 1, 1); - glPopAttrib(); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + glVertexPointer(3, GL_FLOAT, 0, varr); + glNormalPointer(GL_FLOAT, 0, narr); - post_draw(); -} + glDrawArrays(GL_TRIANGLES, 0, nverts); -static void vertex(struct metasurface *ms, float x, float y, float z) -{ - static const float delta = 0.01; + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_NORMAL_ARRAY); - float val = eval(ms, x, y, z); - float dfdx = eval(ms, x + delta, y, z) - val; - float dfdy = eval(ms, x, y + delta, z) - val; - float dfdz = eval(ms, x, y, z + delta) - val; + /* + glDisable(GL_TEXTURE_2D); + glDisable(GL_LIGHTING); - glNormal3f(dfdx, dfdy, dfdz); - glVertex3f(x, y, z); -} + varr = msurf_vertices(priv->msurf); + narr = msurf_normals(priv->msurf); -static float eval(struct metasurface *ms, float x, float y, float z) -{ - float sum = 0.0f; - BlobPriv *priv = (BlobPriv*)msurf_get_user_data(ms); + glBegin(GL_LINES); + glColor3f(0, 1, 0); - for(int i=0; imballs[i].pos.x; - float dy = y - priv->mballs[i].pos.y; - float dz = z - priv->mballs[i].pos.z; - float dsq = dx * dx + dy * dy + dz * dz; + float nscale = 0.2; + for(int i=0; imballs[i].energy / dsq; + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + if(node) { + glPopMatrix(); } - return sum; + + glPopAttrib(); +} + +const AABox &BlobExhibit::get_aabox() const +{ + Box box = Box(priv->vol, node ? node->get_matrix() : Mat4::identity); + calc_bounding_aabox(&aabb, &box); + return aabb; } diff --git a/src/blob_exhibit.h b/src/blob_exhibit.h index 001047a..be279e6 100644 --- a/src/blob_exhibit.h +++ b/src/blob_exhibit.h @@ -18,6 +18,8 @@ public: void update(float dt); void draw() const; + + const AABox &get_aabox() const; }; #endif // BLOB_EXHIBIT_H_ diff --git a/src/blobs/metasurf.c b/src/blobs/metasurf.c index 2207b3d..31efcf4 100644 --- a/src/blobs/metasurf.c +++ b/src/blobs/metasurf.c @@ -1,60 +1,28 @@ -/* -metasurf - a library for implicit surface polygonization -Copyright (C) 2011-2016 John Tsiombikas - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with this program. If not, see . -*/ -/* this is pulled from: https://github.com/jtsiomb/metasurf */ #include #include +#include #include "metasurf.h" #include "mcubes.h" - -#undef USE_MTETRA -#define USE_MCUBES - -#if (defined(USE_MTETRA) && defined(USE_MCUBES)) || (!defined(USE_MTETRA) && !defined(USE_MCUBES)) -#error "pick either USE_MTETRA or USE_MCUBES, not both..." -#endif +#include "logger.h" typedef float vec3[3]; struct metasurface { vec3 min, max; - int res[3]; + int res[3], newres[3]; float thres; - msurf_eval_func_t eval; - msurf_vertex_func_t vertex; - msurf_normal_func_t normal; - void *udata; - float dx, dy, dz; - int flip; + unsigned int flags; + + float *voxels; - vec3 vbuf[3]; - int nverts; + int varr_size, varr_alloc_size; + float *varr, *narr; }; static int msurf_init(struct metasurface *ms); -static void process_cell(struct metasurface *ms, vec3 pos, vec3 sz); -#ifdef USE_MTETRA -static void process_tetra(struct metasurface *ms, int *idx, vec3 *pos, float *val); -#endif -#ifdef USE_MCUBES -static void process_cube(struct metasurface *ms, vec3 *pos, float *val); -#endif +static void process_cell(struct metasurface *ms, int xcell, int ycell, int zcell, vec3 pos, vec3 sz); struct metasurface *msurf_create(void) @@ -72,46 +40,56 @@ struct metasurface *msurf_create(void) void msurf_free(struct metasurface *ms) { - free(ms); + if(ms) { + free(ms->voxels); + free(ms->varr); + free(ms->narr); + free(ms); + } } static int msurf_init(struct metasurface *ms) { + ms->voxels = 0; ms->thres = 0.0; - ms->eval = 0; - ms->vertex = 0; - ms->normal = 0; - ms->udata = 0; ms->min[0] = ms->min[1] = ms->min[2] = -1.0; ms->max[0] = ms->max[1] = ms->max[2] = 1.0; - ms->res[0] = ms->res[1] = ms->res[2] = 40; - ms->nverts = 0; + ms->res[0] = ms->res[1] = ms->res[2] = 0; + ms->newres[0] = ms->newres[1] = ms->newres[2] = 40; + + ms->varr_alloc_size = ms->varr_size = 0; + ms->varr = ms->narr = 0; ms->dx = ms->dy = ms->dz = 0.001; - ms->flip = 0; + ms->flags = 0; return 0; } -void msurf_set_user_data(struct metasurface *ms, void *udata) +void msurf_enable(struct metasurface *ms, unsigned int opt) { - ms->udata = udata; + ms->flags |= opt; } -void *msurf_get_user_data(struct metasurface *ms) +void msurf_disable(struct metasurface *ms, unsigned int opt) { - return ms->udata; + ms->flags &= ~opt; +} + +int msurf_is_enabled(struct metasurface *ms, unsigned int opt) +{ + return ms->flags & opt; } void msurf_set_inside(struct metasurface *ms, int inside) { switch(inside) { case MSURF_GREATER: - ms->flip = 0; + msurf_enable(ms, MSURF_FLIP); break; case MSURF_LESS: - ms->flip = 1; + msurf_disable(ms, MSURF_FLIP); break; default: @@ -121,22 +99,7 @@ void msurf_set_inside(struct metasurface *ms, int inside) int msurf_get_inside(struct metasurface *ms) { - return ms->flip ? MSURF_LESS : MSURF_GREATER; -} - -void msurf_eval_func(struct metasurface *ms, msurf_eval_func_t func) -{ - ms->eval = func; -} - -void msurf_vertex_func(struct metasurface *ms, msurf_vertex_func_t func) -{ - ms->vertex = func; -} - -void msurf_normal_func(struct metasurface *ms, msurf_normal_func_t func) -{ - ms->normal = func; + return msurf_is_enabled(ms, MSURF_FLIP) ? MSURF_LESS : MSURF_GREATER; } void msurf_set_bounds(struct metasurface *ms, float xmin, float ymin, float zmin, float xmax, float ymax, float zmax) @@ -161,9 +124,9 @@ void msurf_get_bounds(struct metasurface *ms, float *xmin, float *ymin, float *z void msurf_set_resolution(struct metasurface *ms, int xres, int yres, int zres) { - ms->res[0] = xres; - ms->res[1] = yres; - ms->res[2] = zres; + ms->newres[0] = xres; + ms->newres[1] = yres; + ms->newres[2] = zres; } void msurf_get_resolution(struct metasurface *ms, int *xres, int *yres, int *zres) @@ -184,112 +147,150 @@ float msurf_get_threshold(struct metasurface *ms) } +float *msurf_voxels(struct metasurface *ms) +{ + if(ms->res[0] != ms->newres[0] || ms->res[1] != ms->newres[1] || ms->res[2] != ms->newres[2]) { + int count; + ms->res[0] = ms->newres[0]; + ms->res[1] = ms->newres[1]; + ms->res[2] = ms->newres[2]; + count = ms->res[0] * ms->res[1] * ms->res[2]; + free(ms->voxels); + if(!(ms->voxels = malloc(count * sizeof *ms->voxels))) { + return 0; + } + } + return ms->voxels; +} + +float *msurf_slice(struct metasurface *ms, int idx) +{ + float *vox = msurf_voxels(ms); + if(!vox) return 0; + + return vox + ms->res[0] * ms->res[1] * idx; +} + int msurf_polygonize(struct metasurface *ms) { int i, j, k; vec3 pos, delta; - if(!ms->eval || !ms->vertex) { - fprintf(stderr, "you need to set eval and vertex callbacks before calling msurf_polygonize\n"); - return -1; - } + if(!ms->voxels) return -1; + + ms->varr_size = 0; for(i=0; i<3; i++) { delta[i] = (ms->max[i] - ms->min[i]) / (float)ms->res[i]; } - pos[0] = ms->min[0]; for(i=0; ires[0] - 1; i++) { - pos[1] = ms->min[1]; for(j=0; jres[1] - 1; j++) { - - pos[2] = ms->min[2]; for(k=0; kres[2] - 1; k++) { - process_cell(ms, pos, delta); + pos[0] = ms->min[0] + i * delta[0]; + pos[1] = ms->min[1] + j * delta[1]; + pos[2] = ms->min[2] + k * delta[2]; - pos[2] += delta[2]; + process_cell(ms, i, j, k, pos, delta); } - pos[1] += delta[1]; } - pos[0] += delta[0]; } return 0; } - -static void process_cell(struct metasurface *ms, vec3 pos, vec3 sz) +int msurf_vertex_count(struct metasurface *ms) { - int i; - vec3 p[8]; - float val[8]; - -#ifdef USE_MTETRA - static int tetra[][4] = { - {0, 2, 3, 7}, - {0, 2, 6, 7}, - {0, 4, 6, 7}, - {0, 6, 1, 2}, - {0, 6, 1, 4}, - {5, 6, 1, 4} - }; -#endif - - static const float offs[][3] = { - {0.0f, 0.0f, 0.0f}, - {1.0f, 0.0f, 0.0f}, - {1.0f, 1.0f, 0.0f}, - {0.0f, 1.0f, 0.0f}, - {0.0f, 0.0f, 1.0f}, - {1.0f, 0.0f, 1.0f}, - {1.0f, 1.0f, 1.0f}, - {0.0f, 1.0f, 1.0f} - }; - - for(i=0; i<8; i++) { - p[i][0] = pos[0] + sz[0] * offs[i][2]; - p[i][1] = pos[1] + sz[1] * offs[i][1]; - p[i][2] = pos[2] + sz[2] * offs[i][0]; - - val[i] = ms->eval(ms, p[i][0], p[i][1], p[i][2]); - } - -#ifdef USE_MTETRA - for(i=0; i<6; i++) { - process_tetra(ms, tetra[i], p, val); - } -#endif -#ifdef USE_MCUBES - process_cube(ms, p, val); -#endif + return ms->varr_size / 3; } +float *msurf_vertices(struct metasurface *ms) +{ + return ms->varr; +} -/* ---- marching cubes implementation ---- */ -#ifdef USE_MCUBES +float *msurf_normals(struct metasurface *ms) +{ + return ms->narr; +} static unsigned int mc_bitcode(float *val, float thres); -static void process_cube(struct metasurface *ms, vec3 *pos, float *val) +static void process_cell(struct metasurface *ms, int xcell, int ycell, int zcell, vec3 cellpos, vec3 cellsz) { + int i, j, k, slice_size; + vec3 pos[8]; + float dfdx[8], dfdy[8], dfdz[8]; + vec3 vert[12], norm[12]; + float val[8]; + float *cellptr; + unsigned int code; + + static const int offs[][3] = { + {0, 0, 0}, + {1, 0, 0}, + {1, 1, 0}, + {0, 1, 0}, + {0, 0, 1}, + {1, 0, 1}, + {1, 1, 1}, + {0, 1, 1} + }; + static const int pidx[12][2] = { {0, 1}, {1, 2}, {2, 3}, {3, 0}, {4, 5}, {5, 6}, {6, 7}, {7, 4}, {0, 4}, {1, 5}, {2, 6}, {3, 7} }; - int i, j; - vec3 vert[12]; - unsigned int code = mc_bitcode(val, ms->thres); - if(ms->flip) { - code = ~code & 0xff; + slice_size = ms->res[0] * ms->res[1]; + cellptr = ms->voxels + slice_size * zcell + ms->res[0] * ycell + xcell; + +#define GRIDOFFS(x, y, z) ((z) * slice_size + (y) * ms->res[0] + (x)) + + for(i=0; i<8; i++) { + val[i] = cellptr[GRIDOFFS(offs[i][0], offs[i][1], offs[i][2])]; } + code = mc_bitcode(val, ms->thres); + if(ms->flags & MSURF_FLIP) { + code = ~code & 0xff; + } if(mc_edge_table[code] == 0) { return; } + /* calculate normals at the 8 corners */ + for(i=0; i<8; i++) { + float *ptr = cellptr + GRIDOFFS(offs[i][0], offs[i][1], offs[i][2]); + + if(xcell < ms->res[0] - 1) { + dfdx[i] = ptr[GRIDOFFS(1, 0, 0)] - *ptr; + } else { + dfdx[i] = *ptr - ptr[GRIDOFFS(-1, 0, 0)]; + } + if(ycell < ms->res[1] - 1) { + dfdy[i] = ptr[GRIDOFFS(0, 1, 0)] - *ptr; + } else { + dfdy[i] = *ptr - ptr[GRIDOFFS(0, -1, 0)]; + } + if(zcell < ms->res[2] - 1) { + dfdz[i] = ptr[GRIDOFFS(0, 0, 1)] - *ptr; + } else { + dfdz[i] = *ptr - ptr[GRIDOFFS(0, 0, -1)]; + } + } + + /* calculate the world-space position of each corner */ + for(i=0; i<8; i++) { + pos[i][0] = cellpos[0] + cellsz[0] * offs[i][0]; + pos[i][1] = cellpos[1] + cellsz[1] * offs[i][1]; + pos[i][2] = cellpos[2] + cellsz[2] * offs[i][2]; + } + + /* generate up to a max of 12 vertices per cube. interpolate positions and normals for each one */ for(i=0; i<12; i++) { if(mc_edge_table[code] & (1 << i)) { + float nx, ny, nz; int p0 = pidx[i][0]; int p1 = pidx[i][1]; @@ -297,29 +298,61 @@ static void process_cube(struct metasurface *ms, vec3 *pos, float *val) vert[i][0] = pos[p0][0] + (pos[p1][0] - pos[p0][0]) * t; vert[i][1] = pos[p0][1] + (pos[p1][1] - pos[p0][1]) * t; vert[i][2] = pos[p0][2] + (pos[p1][2] - pos[p0][2]) * t; + + nx = dfdx[p0] + (dfdx[p1] - dfdx[p0]) * t; + ny = dfdy[p0] + (dfdy[p1] - dfdy[p0]) * t; + nz = dfdz[p0] + (dfdz[p1] - dfdz[p0]) * t; + + if(ms->flags & MSURF_FLIP) { + nx = -nx; + ny = -ny; + nz = -nz; + } + + if(ms->flags & MSURF_NORMALIZE) { + float len = sqrt(nx * nx + ny * ny + nz * nz); + if(len != 0.0) { + float s = 1.0f / len; + nx *= s; + ny *= s; + nz *= s; + } + } + + norm[i][0] = nx; + norm[i][1] = ny; + norm[i][2] = nz; } } + /* for each triangle of the cube add the appropriate vertices to the vertex buffer */ for(i=0; mc_tri_table[code][i] != -1; i+=3) { for(j=0; j<3; j++) { - float *v = vert[mc_tri_table[code][i + j]]; - - if(ms->normal) { - float dfdx, dfdy, dfdz; - dfdx = ms->eval(ms, v[0] - ms->dx, v[1], v[2]) - ms->eval(ms, v[0] + ms->dx, v[1], v[2]); - dfdy = ms->eval(ms, v[0], v[1] - ms->dy, v[2]) - ms->eval(ms, v[0], v[1] + ms->dy, v[2]); - dfdz = ms->eval(ms, v[0], v[1], v[2] - ms->dz) - ms->eval(ms, v[0], v[1], v[2] + ms->dz); - - if(ms->flip) { - dfdx = -dfdx; - dfdy = -dfdy; - dfdz = -dfdz; + int idx = mc_tri_table[code][i + j]; + float *v = vert[idx]; + float *n = norm[idx]; + + /* TODO multithreadied polygon emit */ + if(ms->varr_size + 3 > ms->varr_alloc_size) { + int newsz = ms->varr_alloc_size ? ms->varr_alloc_size * 2 : 1024; + float *new_varr, *new_narr; + + if(!(new_varr = realloc(ms->varr, newsz * sizeof *new_varr)) || + !(new_narr = realloc(ms->narr, newsz * sizeof *new_narr))) { + free(new_varr); + error_log("msurf_polygonize: failed to grow vertex buffers to %d elements\n", newsz); + return; } - ms->normal(ms, dfdx, dfdy, dfdz); + ms->varr = new_varr; + ms->narr = new_narr; + ms->varr_alloc_size = newsz; } - /* TODO multithreadied polygon emmit */ - ms->vertex(ms, v[0], v[1], v[2]); + for(k=0; k<3; k++) { + ms->varr[ms->varr_size] = v[k]; + ms->narr[ms->varr_size] = n[k]; + ++ms->varr_size; + } } } } @@ -335,118 +368,3 @@ static unsigned int mc_bitcode(float *val, float thres) } return res; } -#endif /* USE_MCUBES */ - - -/* ---- marching tetrahedra implementation (incomplete) ---- */ -#ifdef USE_MTETRA - -static unsigned int mt_bitcode(float v0, float v1, float v2, float v3, float thres); -static void emmit(struct metasurface *ms, float v0, float v1, vec3 p0, vec3 p1, int rev) - - -#define REVBIT(x) ((x) & 8) -#define INV(x) (~(x) & 0xf) -#define EDGE(a, b) emmit(ms, val[idx[a]], val[idx[b]], pos[idx[a]], pos[idx[b]], REVBIT(code)) -static void process_tetra(struct metasurface *ms, int *idx, vec3 *pos, float *val) -{ - unsigned int code = mt_bitcode(val[idx[0]], val[idx[1]], val[idx[2]], val[idx[3]], ms->thres); - - switch(code) { - case 1: - case INV(1): - EDGE(0, 1); - EDGE(0, 2); - EDGE(0, 3); - break; - - case 2: - case INV(2): - EDGE(1, 0); - EDGE(1, 3); - EDGE(1, 2); - break; - - case 3: - case INV(3): - EDGE(0, 3); - EDGE(0, 2); - EDGE(1, 3); - - EDGE(1, 3); - EDGE(1, 2); - EDGE(0, 2); - break; - - case 4: - case INV(4): - EDGE(2, 0); - EDGE(2, 1); - EDGE(2, 3); - break; - - case 5: - case INV(5): - EDGE(0, 1); - EDGE(2, 3); - EDGE(0, 3); - - EDGE(0, 1); - EDGE(1, 2); - EDGE(2, 3); - break; - - case 6: - case INV(6): - EDGE(0, 1); - EDGE(1, 3); - EDGE(2, 3); - - EDGE(0, 1); - EDGE(0, 2); - EDGE(2, 3); - break; - - case 7: - case INV(7): - EDGE(3, 0); - EDGE(3, 2); - EDGE(3, 1); - break; - - default: - break; /* cases 0 and 15 */ - } -} - -#define BIT(i) ((v##i > thres) ? (1 << i) : 0) -static unsigned int mt_bitcode(float v0, float v1, float v2, float v3, float thres) -{ - return BIT(0) | BIT(1) | BIT(2) | BIT(3); -} - -static void emmit(struct metasurface *ms, float v0, float v1, vec3 p0, vec3 p1, int rev) -{ - int i; - float t = (ms->thres - v0) / (v1 - v0); - - vec3 p; - for(i=0; i<3; i++) { - p[i] = p0[i] + (p1[i] - p0[i]) * t; - } - ms->vertex(ms, p[0], p[1], p[2]); - - /*for(i=0; i<3; i++) { - ms->vbuf[ms->nverts][i] = p0[i] + (p1[i] - p0[i]) * t; - } - - if(++ms->nverts >= 3) { - ms->nverts = 0; - - for(i=0; i<3; i++) { - int idx = rev ? (2 - i) : i; - ms->vertex(ms, ms->vbuf[idx][0], ms->vbuf[idx][1], ms->vbuf[idx][2]); - } - }*/ -} -#endif /* USE_MTETRA */ diff --git a/src/blobs/metasurf.h b/src/blobs/metasurf.h index 77d2920..3f7ed6e 100644 --- a/src/blobs/metasurf.h +++ b/src/blobs/metasurf.h @@ -1,32 +1,13 @@ -/* -metasurf - a library for implicit surface polygonization -Copyright (C) 2011-2015 John Tsiombikas - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with this program. If not, see . -*/ -/* this is pulled from: https://github.com/jtsiomb/metasurf */ #ifndef METASURF_H_ #define METASURF_H_ #define MSURF_GREATER 1 #define MSURF_LESS 0 -struct metasurface; +#define MSURF_FLIP 1 +#define MSURF_NORMALIZE 2 -typedef float (*msurf_eval_func_t)(struct metasurface *ms, float, float, float); -typedef void (*msurf_vertex_func_t)(struct metasurface *ms, float, float, float); -typedef void (*msurf_normal_func_t)(struct metasurface *ms, float, float, float); +struct metasurface; #ifdef __cplusplus extern "C" { @@ -35,22 +16,14 @@ extern "C" { struct metasurface *msurf_create(void); void msurf_free(struct metasurface *ms); -void msurf_set_user_data(struct metasurface *ms, void *udata); -void *msurf_get_user_data(struct metasurface *ms); +void msurf_enable(struct metasurface *ms, unsigned int opt); +void msurf_disable(struct metasurface *ms, unsigned int opt); +int msurf_is_enabled(struct metasurface *ms, unsigned int opt); /* which is inside above or below the threshold */ void msurf_set_inside(struct metasurface *ms, int inside); int msurf_get_inside(struct metasurface *ms); -/* set a scalar field evaluator function */ -void msurf_eval_func(struct metasurface *ms, msurf_eval_func_t func); - -/* set a generated vertex callback function */ -void msurf_vertex_func(struct metasurface *ms, msurf_vertex_func_t func); - -/* set a generated surface normal callback function (unused yet) */ -void msurf_normal_func(struct metasurface *ms, msurf_normal_func_t func); - /* set the bounding box (default: -1, -1, -1, 1, 1, 1) * keep this as tight as possible to avoid wasting grid resolution */ @@ -67,10 +40,16 @@ void msurf_get_resolution(struct metasurface *ms, int *xres, int *yres, int *zre void msurf_set_threshold(struct metasurface *ms, float thres); float msurf_get_threshold(struct metasurface *ms); +/* get pointer to the scalar field */ +float *msurf_voxels(struct metasurface *ms); +float *msurf_slice(struct metasurface *ms, int idx); /* finally call this to perform the polygonization */ int msurf_polygonize(struct metasurface *ms); +int msurf_vertex_count(struct metasurface *ms); +float *msurf_vertices(struct metasurface *ms); +float *msurf_normals(struct metasurface *ms); #ifdef __cplusplus } diff --git a/src/dbg_gui.cc b/src/dbg_gui.cc index 68832ce..c1abce3 100644 --- a/src/dbg_gui.cc +++ b/src/dbg_gui.cc @@ -8,6 +8,7 @@ static void render_func(ImDrawData *ddat); bool debug_gui, parent_expanded; +SceneNode *dbg_sel_node; static ImGuiIO *io; static Texture *tex; diff --git a/src/dbg_gui.h b/src/dbg_gui.h index f6ff011..69b68aa 100644 --- a/src/dbg_gui.h +++ b/src/dbg_gui.h @@ -3,8 +3,11 @@ #include "imgui/imgui.h" +class SceneNode; + extern bool debug_gui; extern bool parent_expanded; +extern SceneNode *dbg_sel_node; bool init_debug_gui(); void cleanup_debug_gui(); diff --git a/src/exhibit.cc b/src/exhibit.cc index d76b291..2e81854 100644 --- a/src/exhibit.cc +++ b/src/exhibit.cc @@ -2,15 +2,10 @@ #include "snode.h" #include "scene.h" -class ExhibitPriv { -public: - Vec3 orig_pos; - Quat orig_rot; - SceneNode *orig_node; +ExSelection ExSelection::null; - ExhibitPriv(); -}; +// selection ExSelection::ExSelection(Exhibit *ex) { this->ex = ex; @@ -23,59 +18,58 @@ ExSelection::operator bool() const return ex != 0; } -ExhibitPriv::ExhibitPriv() +// Exhibit data +ExData::ExData() { - orig_node = 0; + voice = 0; } +ExData::~ExData() +{ + delete voice; +} + +// exhibit class Exhibit::Exhibit() { - priv = new ExhibitPriv; + orig_parent = 0; + prev_slot = 0; } Exhibit::~Exhibit() { - delete priv; } void Exhibit::set_node(SceneNode *node) { - this->node = priv->orig_node = node; - priv->orig_pos = node->get_position(); - priv->orig_rot = node->get_rotation(); + this->node = node; + orig_parent = node->get_parent(); } ExSelection Exhibit::select(const Ray &ray) const { - return ExSelection(0); + ExSelection sel; + HitPoint hit; + if(get_aabox().intersect(ray, &hit)) { + sel.ex = (Exhibit*)this; + sel.selray = ray; + sel.dist = hit.dist; + sel.validmask = EXSEL_RAY; + } + return sel; } ExSelection Exhibit::select(const Sphere &sph) const { - return ExSelection(0); + return ExSelection(0); // TODO } void Exhibit::update(float dt) { } -void Exhibit::pre_draw() const -{ - if(node) { - glMatrixMode(GL_MODELVIEW); - glPushMatrix(); - glMultMatrixf(node->get_matrix()[0]); - } -} - -void Exhibit::draw() const -{ -} - -void Exhibit::post_draw() const +const AABox &Exhibit::get_aabox() const { - if(node) { - glMatrixMode(GL_MODELVIEW); - glPopMatrix(); - } + aabb = node->get_bounds(); + return aabb; } diff --git a/src/exhibit.h b/src/exhibit.h index 63b2898..9b5c753 100644 --- a/src/exhibit.h +++ b/src/exhibit.h @@ -1,12 +1,14 @@ #ifndef EXHIBIT_H_ #define EXHIBIT_H_ +#include #include #include "object.h" #include "geom.h" +#include "audio/stream.h" class Exhibit; -class ExhibitPriv; +class ExhibitSlot; class Scene; enum { @@ -16,11 +18,14 @@ enum { class ExSelection { public: + static ExSelection null; // null selection + Exhibit *ex; void *obj; void *data; Ray selray; Sphere selsphere; + float dist; unsigned int validmask; ExSelection(Exhibit *ex = 0); @@ -28,6 +33,24 @@ public: operator bool() const; }; +enum { + EXDATA_INFO, + EXDATA_VIDEO +}; + +class ExData { +public: + int type; + + std::string text; + AudioStream *voice; + // TODO: video stream + + ExData(); + ~ExData(); +}; + + /* TODO - select me aktina kai select me sfaira, epistrefei Selection - hover me aktina kai hover me sfaira @@ -35,9 +58,12 @@ public: */ class Exhibit : public Object { private: - ExhibitPriv *priv; + SceneNode *orig_parent; public: + ExhibitSlot *prev_slot; + std::vector data; + Exhibit(); virtual ~Exhibit(); @@ -49,11 +75,9 @@ public: virtual ExSelection select(const Ray &ray) const; virtual ExSelection select(const Sphere &sph) const; - virtual void update(float dt = 0.0f); + virtual void update(float dt = 0.0f) override; - virtual void pre_draw() const; - virtual void draw() const; - virtual void post_draw() const; + virtual const AABox &get_aabox() const override; }; #endif // EXHIBIT_H_ diff --git a/src/exman.cc b/src/exman.cc index d93c2ff..e5cdd9f 100644 --- a/src/exman.cc +++ b/src/exman.cc @@ -1,22 +1,132 @@ +#include #include #include "exman.h" #include "exhibit.h" #include "blob_exhibit.h" #include "treestore.h" +#include "app.h" +#include "geomdraw.h" static Exhibit *create_exhibit(const char *type); +static void clean_desc_text(char *dest, const char *src); + + +ExhibitSlot::ExhibitSlot(Exhibit *ex) +{ + this->ex = 0; + + init(ex); +} + +ExhibitSlot::~ExhibitSlot() +{ + detach_exhibit(); + + SceneNode *par = node.get_parent(); + if(par) { + par->remove_child(&node); + + while(node.get_num_children()) { + par->add_child(node.get_child(0)); + } + } +} + + +void ExhibitSlot::init(Exhibit *ex) +{ + std::string node_name = "ExhibitSlot"; + if(ex) { + if(ex->get_name()) { + node_name += std::string(":") + std::string(ex->get_name()); + } + + if(ex->node) { + if(ex->node->get_parent()) { + ex->node->get_parent()->add_child(&node); + } + node.set_position(ex->node->get_node_position()); + node.set_rotation(ex->node->get_node_rotation()); + ex->node->set_position(Vec3(0, 0, 0)); + ex->node->set_rotation(Quat::identity); + } + attach_exhibit(ex); + } else { + this->ex = 0; + } + + node.set_name(node_name.c_str()); +} + +bool ExhibitSlot::empty() const +{ + return ex == 0; +} + +Exhibit *ExhibitSlot::get_exhibit() const +{ + return ex; +} + +/* In the process of attaching the exhibit, we also steal the exhibit's node + * from its previous parent, and reparent it to the slot's node. As the slot's + * node itself should have been made a child of the original parent of the + * exhibit during init(), the initial state is we're interjecting a null node in + * the scene graph between the exhibit and its original parent. + * + * Attaching to a slot, implicitly detaches from the previous slot. + */ +bool ExhibitSlot::attach_exhibit(Exhibit *ex, ExSlotAttachMode mode) +{ + if(!ex || this->ex) return false; + + if(ex->prev_slot && ex->prev_slot->ex == ex) { + ex->prev_slot->detach_exhibit(); + } + + if(mode != EXSLOT_ATTACH_TRANSIENT) { + ex->prev_slot = this; + } + + node.add_child(ex->node); + this->ex = ex; + return true; +} + +bool ExhibitSlot::detach_exhibit() +{ + if(!ex) return false; + + node.remove_child(ex->node); + ex = 0; + return true; +} + + +// ---- exhibit manager implementation ---- ExhibitManager::ExhibitManager() { + own_scn = 0; } ExhibitManager::~ExhibitManager() { - int num = (int)items.size(); + clear(); +} + +void ExhibitManager::clear() +{ + // not deleting exhibit objects, as they will be deleted the own_scn destructor + items.clear(); + + int num = (int)exslots.size(); for(int i=0; i::iterator it = std::find(items.begin(), items.end(), ex); if(it == items.end()) { items.push_back(ex); + own_scn->add_object(ex); + if(ex->node) own_scn->add_node(ex->node); } } @@ -32,6 +144,8 @@ bool ExhibitManager::remove(Exhibit *ex) std::vector::iterator it = std::find(items.begin(), items.end(), ex); if(it != items.end()) { items.erase(it); + own_scn->remove_object(ex); + if(ex->node) own_scn->remove_node(ex->node); return true; } return false; @@ -46,29 +160,106 @@ bool ExhibitManager::load(MetaScene *mscn, const char *fname) return false; } + /* create our own scene to manage all exhibits not already in an existing scene + * and add it to the metascene. + * Also exhibit drawing happens due to the renderer drawing the metascene + */ + if(!own_scn) { + own_scn = new Scene; + own_scn->name = "ad-hoc exhibits"; + mscn->scenes.push_back(own_scn); + } + struct ts_node *iter = root->child_list; while(iter) { struct ts_node *node = iter; iter = iter->next; - if(strcmp(node->name, "item") == 0) { - SceneNode *snode; - - const char *amatch = ts_get_attr_str(node, "match_node"); - if(!amatch || !(snode = mscn->match_node(amatch))) { - error_log("regexp \"%s\" didn't match any nodes\n", amatch ? amatch : ""); - continue; - } + SceneNode *snode = 0; + if(strcmp(node->name, "item") == 0) { Exhibit *ex; const char *atype = ts_get_attr_str(node, "type"); if(!atype || !(ex = create_exhibit(atype))) { error_log("failed to create exhibit of type: %s\n", atype); continue; } + const char *alabel = ts_get_attr_str(node, "label"); + if(alabel) { + ex->set_name(alabel); + } + + const char *amatch = ts_get_attr_str(node, "match_node"); + if(amatch) { + if(!(snode = mscn->match_node(amatch))) { + error_log("ExhibitManager::load: regexp \"%s\" didn't match any nodes\n", + amatch ? amatch : ""); + continue; + } + } + // add everything to our data structures + // equivalent to add_exhibit(ex), but without all the searching + own_scn->add_object(ex); + if(!snode) { + snode = new SceneNode; + snode->set_name(ex->get_name()); + own_scn->add_node(snode); + } ex->set_node(snode); items.push_back(ex); + + float *apos = ts_get_attr_vec(node, "pos"); + if(apos) { + snode->set_position(Vec3(apos[0], apos[1], apos[2])); + } + float *arot_axis = ts_get_attr_vec(node, "rotaxis"); + if(arot_axis) { + float arot_angle = ts_get_attr_num(node, "rotangle", 0.0f); + Vec3 axis = Vec3(arot_axis[0], arot_axis[1], arot_axis[2]); + Quat q; + q.set_rotation(axis, deg_to_rad(arot_angle)); + snode->set_rotation(q); + } + struct ts_attr *ascale = ts_get_attr(node, "scale"); + if(ascale) { + switch(ascale->val.type) { + case TS_NUMBER: + snode->set_scaling(Vec3(ascale->val.fnum, ascale->val.fnum, ascale->val.fnum)); + break; + case TS_VECTOR: + snode->set_scaling(Vec3(ascale->val.vec[0], ascale->val.vec[1], ascale->val.vec[2])); + default: + break; + } + } + + // create a new slot and attach the exhibit to it + ExhibitSlot *slot = new ExhibitSlot(ex); + exslots.push_back(slot); + + // grab any extra exhibit data + const char *desc = ts_get_attr_str(node, "description"); + const char *voice = ts_get_attr_str(node, "voiceover"); + if(desc || voice) { + ExData exd; + + if(desc) { + char *fixed_desc = new char[strlen(desc) + 1]; + clean_desc_text(fixed_desc, desc); + exd.text = std::string(fixed_desc); + delete [] fixed_desc; + } + if(voice) { + exd.voice = new OggVorbisStream; + if(!exd.voice->open(voice)) { + error_log("failed to open voiceover: %s\n", voice); + delete exd.voice; + exd.voice = 0; + } + } + ex->data.push_back(exd); + } } } @@ -76,21 +267,132 @@ bool ExhibitManager::load(MetaScene *mscn, const char *fname) return true; } +ExSelection ExhibitManager::select(const Ray &ray) const +{ + ExSelection nearest; + nearest.dist = FLT_MAX; + + int nitems = items.size(); + for(int i=0; iselect(ray); + if(sel && sel.dist < nearest.dist) { + nearest = sel; + } + } + + return nearest; +} + +ExSelection ExhibitManager::select(const Sphere &sph) const +{ + ExSelection sel; + if(!items.empty()) { + sel.ex = items[0]; + sel.selsphere = sph; + sel.validmask = EXSEL_SPHERE; + } + return sel; // TODO +} + +// TODO optimize +ExhibitSlot *ExhibitManager::nearest_empty_slot(const Vec3 &pos, float max_dist) const +{ + ExhibitSlot *nearest = 0; + float nearest_sqdist = max_dist * max_dist; + + int nslots = exslots.size(); + for(int i=0; iempty()) continue; + + Vec3 slotpos = slot->node.get_position(); + float dsq = length_sq(pos - slotpos); + if(dsq < nearest_sqdist) { + nearest = slot; + nearest_sqdist = dsq; + } + } + + return nearest; +} + +void ExhibitManager::stash_exhibit(Exhibit *ex) +{ + // make sure it's not already stashed + if(std::find(stashed.begin(), stashed.end(), ex) != stashed.end()) { + return; + } + stashed.push_back(ex); + + ex->prev_slot = 0; + if(ex->node->get_parent()) { + ex->node->get_parent()->remove_child(ex->node); + } +} + void ExhibitManager::update(float dt) { int num = items.size(); for(int i=0; inode && !items[i]->node->get_parent()) { + items[i]->node->update(dt); + } items[i]->update(dt); } } +void ExhibitManager::draw() const +{ + int num = items.size(); + for(int i=0; iget_aabox(); + draw_geom_object(&bvol); + } + } +} + static Exhibit *create_exhibit(const char *type) { if(strcmp(type, "static") == 0) { + debug_log("creating static exhibit\n"); return new Exhibit; } else if(strcmp(type, "blobs") == 0) { - return new BlobExhibit; + debug_log("creating blobs exhibit\n"); + BlobExhibit *b = new BlobExhibit; + if(!b->init()) { + delete b; + error_log("failed to initialize blobs exhibit\n"); + return 0; + } + return b; } error_log("unknown exhibit type: %s\n", type); return 0; } + +/* clean up description text to be more easily layed out later. + * more specifically: + * - remove redundant spaces + * - remove all newlines except as paragraph separators + * - remove all other whitespace chars + * destination buffer must be as large as the source buffer + */ +static void clean_desc_text(char *dest, const char *src) +{ + while(*src) { + if(isspace(*src)) { + if(*src == '\n' && *(src + 1) == '\n') { + *dest++ = '\n'; + } else { + *dest++ = ' '; + } + while(*src && isspace(*src)) ++src; + } else { + *dest++ = *src++; + } + } + *dest = 0; +} diff --git a/src/exman.h b/src/exman.h index d91e374..df3d8bd 100644 --- a/src/exman.h +++ b/src/exman.h @@ -5,14 +5,47 @@ #include "exhibit.h" #include "metascene.h" +//! argument to ExhibitSlot::attach +enum ExSlotAttachMode { + EXSLOT_ATTACH_PERMANENT, + EXSLOT_ATTACH_TRANSIENT +}; + +//! slot which can hold a single exhibit +class ExhibitSlot { +private: + Exhibit *ex; + +public: + SceneNode node; + + ExhibitSlot(Exhibit *ex = 0); + ~ExhibitSlot(); + + void init(Exhibit *ex); + + bool empty() const; + Exhibit *get_exhibit() const; + + bool attach_exhibit(Exhibit *ex, ExSlotAttachMode mode = EXSLOT_ATTACH_PERMANENT); + bool detach_exhibit(); +}; + + class ExhibitManager { private: - std::vector items; + std::vector items, stashed; + std::vector exslots; + // TODO kdtree of slots for quick nearest queries + + Scene *own_scn; // scene to manage all exhibits not taken from an existing scene public: ExhibitManager(); ~ExhibitManager(); + void clear(); + void add(Exhibit *ex); bool remove(Exhibit *ex); @@ -21,7 +54,12 @@ public: ExSelection select(const Ray &ray) const; ExSelection select(const Sphere &sph) const; + ExhibitSlot *nearest_empty_slot(const Vec3 &pos, float max_dist = 10) const; + + void stash_exhibit(Exhibit *ex); + void update(float dt = 0.0f); + void draw() const; }; #endif // EXMAN_H_ diff --git a/src/geom.cc b/src/geom.cc index 55cfed7..5fc3dce 100644 --- a/src/geom.cc +++ b/src/geom.cc @@ -1,53 +1,54 @@ #include #include #include "geom.h" +#include "app.h" + +#define SPHERE(ptr) ((Sphere*)ptr) +#define AABOX(ptr) ((AABox*)ptr) +#define BOX(ptr) ((Box*)ptr) +#define PLANE(ptr) ((Plane*)ptr) + +GeomObject::GeomObject() +{ + type = GOBJ_UNKNOWN; +} GeomObject::~GeomObject() { } +bool GeomObject::valid() const +{ + return true; +} + +void GeomObject::invalidate() +{ +} + Sphere::Sphere() { + type = GOBJ_SPHERE; radius = 1.0; } Sphere::Sphere(const Vec3 ¢, float radius) : center(cent) { + type = GOBJ_SPHERE; this->radius = radius; } -GeomObjectType Sphere::get_type() const -{ - return GOBJ_SPHERE; -} - -void Sphere::set_union(const GeomObject *obj1, const GeomObject *obj2) +bool Sphere::valid() const { - if(obj1->get_type() != GOBJ_SPHERE || obj2->get_type() != GOBJ_SPHERE) { - fprintf(stderr, "Sphere::set_union: arguments must be spheres"); - return; - } - const Sphere *sph1 = (const Sphere*)obj1; - const Sphere *sph2 = (const Sphere*)obj2; - - float dist = length(sph1->center - sph2->center); - float surf_dist = dist - (sph1->radius + sph2->radius); - float d1 = sph1->radius + surf_dist / 2.0; - float d2 = sph2->radius + surf_dist / 2.0; - float t = d1 / (d1 + d2); - - if(t < 0.0) t = 0.0; - if(t > 1.0) t = 1.0; - - center = sph1->center * t + sph2->center * (1.0 - t); - radius = std::max(dist * t + sph2->radius, dist * (1.0f - t) + sph1->radius); + return radius >= 0.0f; } -void Sphere::set_intersection(const GeomObject *obj1, const GeomObject *obj2) +void Sphere::invalidate() { - fprintf(stderr, "Sphere::intersection undefined\n"); + center = Vec3(0, 0, 0); + radius = -1; } bool Sphere::intersect(const Ray &ray, HitPoint *hit) const @@ -95,64 +96,60 @@ bool Sphere::contains(const Vec3 &pt) const float Sphere::distance(const Vec3 &v) const { + return std::max(length(v - center) - radius, 0.0f); +} + +float Sphere::signed_distance(const Vec3 &v) const +{ return length(v - center) - radius; } AABox::AABox() { + type = GOBJ_AABOX; } AABox::AABox(const Vec3 &vmin, const Vec3 &vmax) : min(vmin), max(vmax) { + type = GOBJ_AABOX; } -GeomObjectType AABox::get_type() const +bool AABox::valid() const { - return GOBJ_AABOX; + return min.x <= max.x && min.y <= max.y && min.z <= max.z; } -void AABox::set_union(const GeomObject *obj1, const GeomObject *obj2) +void AABox::invalidate() { - if(obj1->get_type() != GOBJ_AABOX || obj2->get_type() != GOBJ_AABOX) { - fprintf(stderr, "AABox::set_union: arguments must be AABoxes too\n"); - return; - } - const AABox *box1 = (const AABox*)obj1; - const AABox *box2 = (const AABox*)obj2; - - min.x = std::min(box1->min.x, box2->min.x); - min.y = std::min(box1->min.y, box2->min.y); - min.z = std::min(box1->min.z, box2->min.z); - - max.x = std::max(box1->max.x, box2->max.x); - max.y = std::max(box1->max.y, box2->max.y); - max.z = std::max(box1->max.z, box2->max.z); + min = Vec3(FLT_MAX, FLT_MAX, FLT_MAX); + max = Vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX); } -void AABox::set_intersection(const GeomObject *obj1, const GeomObject *obj2) +Vec3 AABox::get_corner(int idx) const { - if(obj1->get_type() != GOBJ_AABOX || obj2->get_type() != GOBJ_AABOX) { - fprintf(stderr, "AABox::set_intersection: arguments must be AABoxes too\n"); - return; - } - const AABox *box1 = (const AABox*)obj1; - const AABox *box2 = (const AABox*)obj2; - - for(int i=0; i<3; i++) { - min[i] = std::max(box1->min[i], box2->min[i]); - max[i] = std::min(box1->max[i], box2->max[i]); - - if(max[i] < min[i]) { - max[i] = min[i]; - } - } + Vec3 v[] = {min, max}; + static const int xidx[] = {0, 1, 1, 0, 0, 1, 1, 0}; + static const int yidx[] = {0, 0, 0, 0, 1, 1, 1, 1}; + static const int zidx[] = {0, 0, 1, 1, 0, 0, 1, 1}; + return Vec3(v[xidx[idx]].x, v[yidx[idx]].y, v[zidx[idx]].z); } bool AABox::intersect(const Ray &ray, HitPoint *hit) const { Vec3 param[2] = {min, max}; +#ifndef NDEBUG + Vec3 inv_dir; + if(fpexcept_enabled) { + inv_dir.x = ray.dir.x == 0.0f ? 1.0f : 1.0f / ray.dir.x; + inv_dir.y = ray.dir.y == 0.0f ? 1.0f : 1.0f / ray.dir.y; + inv_dir.z = ray.dir.z == 0.0f ? 1.0f : 1.0f / ray.dir.z; + } else { + inv_dir = Vec3(1.0 / ray.dir.x, 1.0 / ray.dir.y, 1.0 / ray.dir.z); + } +#else Vec3 inv_dir(1.0 / ray.dir.x, 1.0 / ray.dir.y, 1.0 / ray.dir.z); +#endif int sign[3] = {inv_dir.x < 0, inv_dir.y < 0, inv_dir.z < 0}; float tmin = (param[sign[0]].x - ray.origin.x) * inv_dir.x; @@ -225,45 +222,132 @@ float AABox::distance(const Vec3 &v) const return 0.0; // TODO } +float AABox::signed_distance(const Vec3 &v) const +{ + return 0.0; // TODO +} + +Box::Box() +{ + type = GOBJ_BOX; +} + +Box::Box(const AABox &aabox, const Mat4 &xform) + : xform(xform) +{ + type = GOBJ_BOX; + min = aabox.min; + max = aabox.max; +} + +void Box::invalidate() +{ + AABox::invalidate(); + xform = Mat4::identity; +} + +Box::Box(const Vec3 &min, const Vec3 &max) + : AABox(min, max) +{ + type = GOBJ_BOX; +} + +Box::Box(const Vec3 &min, const Vec3 &max, const Mat4 &xform) + : AABox(min, max), xform(xform) +{ + type = GOBJ_BOX; +} + +// XXX all this shit is completely untested +Box::Box(const Vec3 &pos, const Vec3 &vi, const Vec3 &vj, const Vec3 &vk) +{ + type = GOBJ_BOX; + float ilen = length(vi); + float jlen = length(vj); + float klen = length(vk); + + min = Vec3(-ilen, -jlen, -klen); + max = Vec3(ilen, jlen, klen); + + float si = ilen == 0.0 ? 1.0 : 1.0 / ilen; + float sj = jlen == 0.0 ? 1.0 : 1.0 / jlen; + float sk = klen == 0.0 ? 1.0 : 1.0 / klen; + + xform = Mat4(vi * si, vj * sj, vk * sk); + xform.translate(pos); +} + +Box::Box(const Vec3 *varr, int vcount) +{ + type = GOBJ_BOX; + calc_bounding_aabox(this, varr, vcount); +} + +Vec3 Box::get_corner(int idx) const +{ + return xform * AABox::get_corner(idx); +} + +bool Box::intersect(const Ray &ray, HitPoint *hit) const +{ + Mat4 inv_xform = inverse(xform); + Mat4 dir_inv_xform = inv_xform.upper3x3(); + Mat4 dir_xform = transpose(dir_inv_xform); + Ray local_ray = Ray(inv_xform * ray.origin, dir_inv_xform * ray.dir); + + bool res = AABox::intersect(local_ray, hit); + if(!res || !hit) return res; + + hit->pos = xform * hit->pos; + hit->normal = dir_xform * hit->normal; + hit->local_ray = local_ray; + hit->ray = ray; + return true; +} + +bool Box::contains(const Vec3 &pt) const +{ + // XXX is it faster to extract 6 planes and do dot products? sounds marginal + return AABox::contains(inverse(xform) * pt); +} + +float Box::distance(const Vec3 &v) const +{ + return 0.0f; // TODO +} + +float Box::signed_distance(const Vec3 &v) const +{ + return 0.0f; // TODO +} Plane::Plane() : normal(0.0, 1.0, 0.0) { + type = GOBJ_PLANE; } Plane::Plane(const Vec3 &p, const Vec3 &norm) : pt(p) { + type = GOBJ_PLANE; normal = normalize(norm); } Plane::Plane(const Vec3 &p1, const Vec3 &p2, const Vec3 &p3) : pt(p1) { + type = GOBJ_PLANE; normal = normalize(cross(p2 - p1, p3 - p1)); } Plane::Plane(const Vec3 &normal, float dist) { + type = GOBJ_PLANE; this->normal = normalize(normal); pt = this->normal * dist; } -GeomObjectType Plane::get_type() const -{ - return GOBJ_PLANE; -} - -void Plane::set_union(const GeomObject *obj1, const GeomObject *obj2) -{ - fprintf(stderr, "Plane::set_union undefined\n"); -} - -void Plane::set_intersection(const GeomObject *obj1, const GeomObject *obj2) -{ - fprintf(stderr, "Plane::set_intersection undefined\n"); -} - bool Plane::intersect(const Ray &ray, HitPoint *hit) const { float ndotdir = dot(normal, ray.dir); @@ -290,5 +374,332 @@ bool Plane::contains(const Vec3 &v) const float Plane::distance(const Vec3 &v) const { + return std::max(dot(v - pt, normal), 0.0f); +} + +float Plane::signed_distance(const Vec3 &v) const +{ return dot(v - pt, normal); } + + +Disc::Disc() +{ + type = GOBJ_DISC; + radius = 1.0; +} + +Disc::Disc(const Vec3 &pt, const Vec3 &normal, float rad) + : Plane(pt, normal) +{ + type = GOBJ_DISC; + radius = rad; +} + +Disc::Disc(const Vec3 &normal, float dist, float rad) + : Plane(normal, dist) +{ + type = GOBJ_DISC; + radius = rad; +} + +bool Disc::valid() const +{ + return radius >= 0.0f; +} + +void Disc::invalidate() +{ + radius = -1; +} + +bool Disc::intersect(const Ray &ray, HitPoint *hit) const +{ + HitPoint phit; + if(Plane::intersect(ray, &phit)) { + if(length_sq(phit.pos - pt) <= radius * radius) { + *hit = phit; + return true; + } + } + return false; +} + +bool Disc::contains(const Vec3 &pt) const +{ + Vec3 pj = proj_point_plane(pt, *this); + return length_sq(pj - this->pt) <= radius * radius; +} + +float Disc::distance(const Vec3 &v) const +{ + return 0.0; // TODO +} + +float Disc::signed_distance(const Vec3 &v) const +{ + return 0.0; // TODO +} + + +Vec3 proj_point_plane(const Vec3 &pt, const Plane &plane) +{ + float dist = plane.signed_distance(pt); + return pt - plane.normal * dist; +} + +// ---- bounding sphere calculations ---- + +bool calc_bounding_sphere(Sphere *sph, const GeomObject *obj) +{ + if(!obj->valid()) { + sph->invalidate(); + return true; + } + + switch(obj->type) { + case GOBJ_SPHERE: + *sph = *(Sphere*)obj; + break; + + case GOBJ_AABOX: + sph->center = (AABOX(obj)->min + AABOX(obj)->max) * 0.5; + sph->radius = length(AABOX(obj)->max - AABOX(obj)->min) * 0.5; + break; + + case GOBJ_BOX: + sph->center = (BOX(obj)->min + BOX(obj)->max) * 0.5 + BOX(obj)->xform.get_translation(); + sph->radius = length(BOX(obj)->max - BOX(obj)->min) * 0.5; + break; + + case GOBJ_PLANE: + default: + return false; + } + return true; +} + +bool calc_bounding_sphere(Sphere *sph, const GeomObject *a, const GeomObject *b) +{ + Sphere bsa, bsb; + + if(!calc_bounding_sphere(&bsa, a) || !calc_bounding_sphere(&bsb, b)) { + return false; + } + + float dist = length(bsa.center - bsb.center); + float surf_dist = dist - (bsa.radius + bsb.radius); + float d1 = bsa.radius + surf_dist / 2.0; + float d2 = bsb.radius + surf_dist / 2.0; + float t = d1 / (d1 + d2); + + if(t < 0.0) t = 0.0; + if(t > 1.0) t = 1.0; + + sph->center = bsa.center * t + bsb.center * (1.0 - t); + sph->radius = std::max(dist * t + bsb.radius, dist * (1.0f - t) + bsa.radius); + return true; +} + +bool calc_bounding_sphere(Sphere *sph, const GeomObject **objv, int num) +{ + if(num <= 0) return false; + + if(!calc_bounding_sphere(sph, objv[0])) { + return false; + } + + for(int i=1; icenter = Vec3(0.0, 0.0, 0.0); + for(int i=0; icenter += xform * v[i]; + } + sph->center /= (float)num; + + float rad_sq = 0.0f; + for(int i=0; icenter; + rad_sq = std::max(rad_sq, dot(dir, dir)); + } + sph->radius = sqrt(rad_sq); + return true; +} + +bool calc_bounding_aabox(AABox *box, const GeomObject *obj) +{ + if(!obj->valid()) { + box->invalidate(); + return true; + } + + switch(obj->type) { + case GOBJ_AABOX: + *box = *(AABox*)obj; + break; + + case GOBJ_BOX: + { + Vec3 v[8]; + for(int i=0; i<8; i++) { + v[i] = BOX(obj)->get_corner(i); + } + calc_bounding_aabox(box, v, 8); + } + break; + + case GOBJ_SPHERE: + { + float r = SPHERE(obj)->radius; + box->min = SPHERE(obj)->center - Vec3(r, r, r); + box->max = SPHERE(obj)->center + Vec3(r, r, r); + } + break; + + case GOBJ_PLANE: + default: + return false; + } + return true; +} + +bool calc_bounding_aabox(AABox *box, const GeomObject *a, const GeomObject *b) +{ + AABox bba, bbb; + + if(!calc_bounding_aabox(&bba, a) || !calc_bounding_aabox(&bbb, b)) { + return false; + } + + for(int i=0; i<3; i++) { + box->min[i] = std::min(bba.min[i], bbb.min[i]); + box->max[i] = std::max(bba.max[i], bbb.max[i]); + } + return true; +} + +bool calc_bounding_aabox(AABox *box, const GeomObject **objv, int num) +{ + if(num <= 0) return false; + + if(!calc_bounding_aabox(box, objv[0])) { + return false; + } + + for(int i=1; imin = box->max = xform * v[0]; + for(int i=1; imin[j] = std::min(box->min[j], p[j]); + box->max[j] = std::max(box->max[j], p[j]); + } + } + return true; +} + +bool calc_bounding_box(Box *box, const GeomObject *obj) +{ + if(!obj->valid()) { + box->invalidate(); + return true; + } + + switch(obj->type) { + case GOBJ_BOX: + *box = *(Box*)obj; + break; + + case GOBJ_AABOX: + box->min = BOX(obj)->min; + box->max = BOX(obj)->max; + box->xform = Mat4::identity; + break; + + case GOBJ_SPHERE: + { + float r = SPHERE(obj)->radius; + box->min = SPHERE(obj)->center - Vec3(r, r, r); + box->max = SPHERE(obj)->center + Vec3(r, r, r); + box->xform = Mat4::identity; + } + break; + + case GOBJ_PLANE: + default: + return false; + } + return true; +} + +bool intersect_sphere_sphere(Disc *result, const Sphere &a, const Sphere &b) +{ + Vec3 dir = b.center - a.center; + + float dist_sq = length_sq(dir); + if(dist_sq <= 1e-8) return false; + + float rsum = a.radius + b.radius; + float rdif = fabs(a.radius - b.radius); + if(dist_sq > rsum * rsum || dist_sq < rdif * rdif) { + return false; + } + + float dist = sqrt(dist_sq); + float t = (dist_sq + a.radius * a.radius - b.radius * b.radius) / (2.0 * sqrt(dist_sq)); + + result->pt = a.center + dir * t; + result->normal = dir / dist; + result->radius = sin(acos(t)) * a.radius; + return true; +} + +bool intersect_plane_plane(Ray *result, const Plane &a, const Plane &b) +{ + return false; // TODO +} + +bool intersect_sphere_plane(Sphere *result, const Sphere &s, const Plane &p) +{ + return false; // TODO +} + +bool intersect_plane_sphere(Sphere *result, const Plane &p, const Sphere &s) +{ + return false; // TODO +} + +bool intersect_aabox_aabox(AABox *res, const AABox &a, const AABox &b) +{ + for(int i=0; i<3; i++) { + res->min[i] = std::max(a.min[i], b.min[i]); + res->max[i] = std::min(a.max[i], b.max[i]); + + if(res->max[i] < res->min[i]) { + res->max[i] = res->min[i]; + } + } + return res->min.x != res->max.x && res->min.y != res->max.y && res->min.z != res->max.z; +} diff --git a/src/geom.h b/src/geom.h index e110193..5db1996 100644 --- a/src/geom.h +++ b/src/geom.h @@ -1,13 +1,19 @@ #ifndef GEOMOBJ_H_ #define GEOMOBJ_H_ +/* TODO: + * - implement distance functions + */ + #include enum GeomObjectType { GOBJ_UNKNOWN, GOBJ_SPHERE, GOBJ_AABOX, - GOBJ_PLANE + GOBJ_BOX, + GOBJ_PLANE, + GOBJ_DISC }; class GeomObject; @@ -23,16 +29,19 @@ struct HitPoint { class GeomObject { public: + GeomObjectType type; + + GeomObject(); virtual ~GeomObject(); - virtual GeomObjectType get_type() const = 0; - virtual void set_union(const GeomObject *obj1, const GeomObject *obj2) = 0; - virtual void set_intersection(const GeomObject *obj1, const GeomObject *obj2) = 0; + virtual bool valid() const; + virtual void invalidate(); virtual bool intersect(const Ray &ray, HitPoint *hit = 0) const = 0; virtual bool contains(const Vec3 &pt) const = 0; virtual float distance(const Vec3 &v) const = 0; + virtual float signed_distance(const Vec3 &v) const = 0; }; class Sphere : public GeomObject { @@ -43,15 +52,14 @@ public: Sphere(); Sphere(const Vec3 ¢er, float radius); - GeomObjectType get_type() const; - - void set_union(const GeomObject *obj1, const GeomObject *obj2); - void set_intersection(const GeomObject *obj1, const GeomObject *obj2); + virtual bool valid() const; + virtual void invalidate(); - bool intersect(const Ray &ray, HitPoint *hit = 0) const; - bool contains(const Vec3 &pt) const; + virtual bool intersect(const Ray &ray, HitPoint *hit = 0) const; + virtual bool contains(const Vec3 &pt) const; - float distance(const Vec3 &v) const; + virtual float distance(const Vec3 &v) const; + virtual float signed_distance(const Vec3 &v) const; }; class AABox : public GeomObject { @@ -61,17 +69,41 @@ public: AABox(); AABox(const Vec3 &min, const Vec3 &max); - GeomObjectType get_type() const; + virtual bool valid() const; + virtual void invalidate(); - void set_union(const GeomObject *obj1, const GeomObject *obj2); - void set_intersection(const GeomObject *obj1, const GeomObject *obj2); + virtual Vec3 get_corner(int idx) const; - bool intersect(const Ray &ray, HitPoint *hit = 0) const; - bool contains(const Vec3 &pt) const; + virtual bool intersect(const Ray &ray, HitPoint *hit = 0) const; + virtual bool contains(const Vec3 &pt) const; - float distance(const Vec3 &v) const; + virtual float distance(const Vec3 &v) const; + virtual float signed_distance(const Vec3 &v) const; }; +class Box : public AABox { +public: + Mat4 xform; + + Box(); + Box(const AABox &aabox, const Mat4 &xform); + Box(const Vec3 &min, const Vec3 &max); + Box(const Vec3 &min, const Vec3 &max, const Mat4 &xform); + Box(const Vec3 &pos, const Vec3 &vi, const Vec3 &vj, const Vec3 &vk); + Box(const Vec3 *varr, int vcount); + + virtual void invalidate(); + + virtual Vec3 get_corner(int idx) const; + + virtual bool intersect(const Ray &ray, HitPoint *hit = 0) const; + virtual bool contains(const Vec3 &pt) const; + + virtual float distance(const Vec3 &v) const; + virtual float signed_distance(const Vec3 &v) const; +}; + + class Plane : public GeomObject { public: Vec3 pt, normal; @@ -81,15 +113,68 @@ public: Plane(const Vec3 &p1, const Vec3 &p2, const Vec3 &p3); Plane(const Vec3 &normal, float dist); - GeomObjectType get_type() const; + virtual bool intersect(const Ray &ray, HitPoint *hit = 0) const; + virtual bool contains(const Vec3 &pt) const; - void set_union(const GeomObject *obj1, const GeomObject *obj2); - void set_intersection(const GeomObject *obj1, const GeomObject *obj2); + virtual float distance(const Vec3 &v) const; + virtual float signed_distance(const Vec3 &v) const; +}; - bool intersect(const Ray &ray, HitPoint *hit = 0) const; - bool contains(const Vec3 &pt) const; +class Disc : public Plane { +public: + float radius; - float distance(const Vec3 &v) const; + Disc(); + Disc(const Vec3 &pt, const Vec3 &normal, float rad); + Disc(const Vec3 &normal, float dist, float rad); + + virtual bool valid() const; + virtual void invalidate(); + + virtual bool intersect(const Ray &ray, HitPoint *hit = 0) const; + //! true if the projection of pt to the plane is contained within the disc radius + virtual bool contains(const Vec3 &pt) const; + + virtual float distance(const Vec3 &v) const; + virtual float signed_distance(const Vec3 &v) const; }; +//! project point to plane +Vec3 proj_point_plane(const Vec3 &pt, const Plane &plane); + +//! calculate the bounding sphere of any object +bool calc_bounding_sphere(Sphere *sph, const GeomObject *obj); +//! calculate the bounding sphere of two objects +bool calc_bounding_sphere(Sphere *sph, const GeomObject *a, const GeomObject *b); +//! calculate the bounding sphere of multiple objects +bool calc_bounding_sphere(Sphere *sph, const GeomObject **objv, int num); +//! calculate the bounding sphere of multiple points, optionally transformed by `xform` +bool calc_bounding_sphere(Sphere *sph, const Vec3 *v, int num, const Mat4 &xform = Mat4::identity); + +//! calculate the bounding axis-aligned box of any object +bool calc_bounding_aabox(AABox *box, const GeomObject *obj); +//! calculate the bounding axis-aligned box of two objects +bool calc_bounding_aabox(AABox *box, const GeomObject *a, const GeomObject *b); +//! calculate the bounding axis-aligned box of multiple objects +bool calc_bounding_aabox(AABox *box, const GeomObject **objv, int num); +//! calculate the bounding axis-aligned box of multiple points, optionally transformed by `xform` +bool calc_bounding_aabox(AABox *box, const Vec3 *v, int num, const Mat4 &xform = Mat4::identity); + +//! calculate the bounding box of any object +bool calc_bounding_box(Box *box, const GeomObject *obj); + +//! calculate the intersection plane of two spheres. false if there is no plane or infinite planes. +bool intersect_sphere_sphere(Plane *result, const Sphere &a, const Sphere &b); +//! calculate the intersection line of two planes. returns false if planes are exactly parallel. +bool intersect_plane_plane(Ray *result, const Plane &a, const Plane &b); +/*! calculate the intesection circle of a plane and a sphere. returns false if they don't intersect. + * \{ + */ +bool intersect_sphere_plane(Sphere *result, const Sphere &s, const Plane &p); +bool intersect_plane_sphere(Sphere *result, const Plane &p, const Sphere &s); +//! \} + +//! calculate the intersection of two axis-aligned bounding boxes +bool intersect_aabox_aabox(AABox *res, const AABox &a, const AABox &b); + #endif // GEOMOBJ_H_ diff --git a/src/geomdraw.cc b/src/geomdraw.cc new file mode 100644 index 0000000..6dc25ce --- /dev/null +++ b/src/geomdraw.cc @@ -0,0 +1,81 @@ +#include "geomdraw.h" +#include "opengl.h" +#include "logger.h" +#include "shader.h" + +static void draw_sphere(const Sphere *sph); +static void draw_box(const Box *box); +static void draw_plane(const Plane *plane); + +void draw_geom_object(const GeomObject *gobj) +{ + switch(gobj->type) { + case GOBJ_SPHERE: + draw_sphere((Sphere*)gobj); + break; + + case GOBJ_AABOX: + { + Box box = Box(*(AABox*)gobj, Mat4::identity); + draw_box(&box); + } + break; + + case GOBJ_BOX: + draw_box((Box*)gobj); + break; + + case GOBJ_PLANE: + draw_plane((Plane*)gobj); + break; + + default: + break; + } +} + +static void draw_sphere(const Sphere *sph) +{ + // TODO + warning_log("draw_sphere unimplemented\n"); +} + +static void draw_box(const Box *box) +{ + static const int edges[][2] = { + {0, 1}, {1, 2}, {2, 3}, {3, 0}, + {4, 5}, {5, 6}, {6, 7}, {7, 4}, + {0, 4}, {1, 5}, {2, 6}, {3, 7} + }; + + bind_shader(0); + + glPushAttrib(GL_ENABLE_BIT); + glDisable(GL_LIGHTING); + glDisable(GL_TEXTURE_2D); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glMultMatrixf(box->xform[0]); + + glLineWidth(1.0); + glBegin(GL_LINES); + glColor3f(1, 1, 0); + for(int i=0; i<12; i++) { + Vec3 a = box->get_corner(edges[i][0]); + Vec3 b = box->get_corner(edges[i][1]); + + glVertex3f(a.x, a.y, a.z); + glVertex3f(b.x, b.y, b.z); + } + glEnd(); + + glPopMatrix(); + glPopAttrib(); +} + +static void draw_plane(const Plane *plane) +{ + // TODO + warning_log("draw_plane unimplemented\n"); +} diff --git a/src/geomdraw.h b/src/geomdraw.h new file mode 100644 index 0000000..05e86e6 --- /dev/null +++ b/src/geomdraw.h @@ -0,0 +1,8 @@ +#ifndef GEOMDRAW_H_ +#define GEOMDRAW_H_ + +#include "geom.h" + +void draw_geom_object(const GeomObject *gobj); + +#endif diff --git a/src/logger.cc b/src/logger.cc index 015c9bd..10e4ced 100644 --- a/src/logger.cc +++ b/src/logger.cc @@ -1,4 +1,5 @@ #include +#include #include #include "logger.h" #include "ui.h" @@ -14,11 +15,17 @@ enum { LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_FATAL, LOG_DEBUG }; static int typecolor(int type); +static const char *typeprefix(int type); static FILE *fp = stdout; +static FILE *logfile; static void logmsg(int type, const char *fmt, va_list ap) { + va_list ap_orig; + + va_copy(ap_orig, ap); + #if defined(unix) || defined(__unix__) || (defined(__APPLE__) && !defined(TARGET_IPHONE)) if(isatty(fileno(fp)) && type != LOG_INFO) { int c = typecolor(type); @@ -28,20 +35,48 @@ static void logmsg(int type, const char *fmt, va_list ap) } else #endif { + fprintf(fp, "%s", typeprefix(type)); vfprintf(fp, fmt, ap); } if(type == LOG_ERROR || type == LOG_FATAL || type == LOG_DEBUG) { fflush(fp); } + if(logfile) { + va_end(ap); + va_copy(ap, ap_orig); + fprintf(logfile, "%s", typeprefix(type)); + vfprintf(logfile, fmt, ap); + } + #ifdef WIN32 if(type == LOG_FATAL) { static char msgbuf[1024]; - vsnprintf(msgbuf, sizeof msgbuf - 1, fmt, ap); + vsnprintf(msgbuf, sizeof msgbuf - 1, fmt, ap_orig); msgbuf[sizeof msgbuf - 1] = 0; MessageBox(0, msgbuf, "Fatal error", MB_OK | MB_ICONSTOP); } #endif + + va_end(ap_orig); +} + +static void close_logfile(void) +{ + if(logfile) fclose(logfile); +} + +extern "C" void set_log_file(const char *fname) +{ + static int init_once; + + close_logfile(); + logfile = fopen(fname, "w"); + + if(!init_once) { + atexit(close_logfile); + init_once = 1; + } } extern "C" void info_log(const char *fmt, ...) @@ -127,3 +162,20 @@ static int typecolor(int type) } return 37; } + +static const char *typeprefix(int type) +{ + switch(type) { + case LOG_ERROR: + return "E: "; + case LOG_FATAL: + return "F: "; + case LOG_WARNING: + return "W: "; + case LOG_DEBUG: + return "D: "; + default: + break; + } + return ""; +} diff --git a/src/logger.h b/src/logger.h index 978d634..28821cd 100644 --- a/src/logger.h +++ b/src/logger.h @@ -1,10 +1,14 @@ #ifndef LOGGER_H_ #define LOGGER_H_ +#include + #ifdef __cplusplus extern "C" { #endif +void set_log_file(const char *fname); + void info_log(const char *fmt, ...); void warning_log(const char *fmt, ...); void error_log(const char *fmt, ...); diff --git a/src/main.cc b/src/main.cc index c497dc1..e3c8a73 100644 --- a/src/main.cc +++ b/src/main.cc @@ -32,6 +32,9 @@ int main(int argc, char **argv) SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 8); SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, 1); +#ifndef NDEBUG + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); +#endif int defpos = SDL_WINDOWPOS_UNDEFINED; unsigned int sdlflags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI; @@ -154,8 +157,6 @@ bool app_is_mouse_grabbed() static bool init(int argc, char **argv) { - glewInit(); - if(!app_init(argc, argv)) { return false; } diff --git a/src/mesh.h b/src/mesh.h index 90cd1e7..2e43781 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -221,6 +221,7 @@ public: * @{ */ float get_bsphere(Vec3 *center, float *rad) const; const Sphere &get_bsphere() const; + /// @} static void set_intersect_mode(unsigned int mode); static unsigned int get_intersect_mode(); diff --git a/src/metascene.cc b/src/metascene.cc index 64946cb..d9f96fb 100644 --- a/src/metascene.cc +++ b/src/metascene.cc @@ -78,6 +78,14 @@ void MetaScene::update(float dt) static char text[256]; if(debug_gui) { ImGui::Begin("MetaScene nodes", 0, 0); + ImGui::Columns(2); + + static bool once; + if(!once) { + float x = ImGui::GetColumnOffset(1); + ImGui::SetColumnOffset(1, x * 1.7); + once = true; + } } int nscn = scenes.size(); @@ -90,6 +98,8 @@ void MetaScene::update(float dt) sprintf(text, "scene %3d: %s", i, scenes[i]->name.c_str()); } expanded = parent_expanded = ImGui::TreeNode(text); + ImGui::NextColumn(); + ImGui::NextColumn(); } scenes[i]->update(dt); @@ -100,10 +110,12 @@ void MetaScene::update(float dt) } if(debug_gui) { + ImGui::Columns(1); ImGui::End(); } } +// XXX not used, renderer draws void MetaScene::draw() const { int nscn = scenes.size(); diff --git a/src/object.cc b/src/object.cc index d850d47..2bc8b79 100644 --- a/src/object.cc +++ b/src/object.cc @@ -35,3 +35,8 @@ void Object::update(float dt) void Object::draw() const { } + +const AABox &Object::get_aabox() const +{ + return aabb; +} diff --git a/src/object.h b/src/object.h index cc82d1b..3ffec1f 100644 --- a/src/object.h +++ b/src/object.h @@ -12,12 +12,12 @@ class SceneNode; enum ObjType { OBJ_NULL, OBJ_MESH }; class Object { -private: +protected: std::string name; + mutable AABox aabb; public: Material mtl; - //GeomObject *bvol; SceneNode *node; Object(); @@ -32,6 +32,8 @@ public: virtual void update(float dt = 0.0f); virtual void draw() const; + + virtual const AABox &get_aabox() const; }; #endif // OBJECT_H_ diff --git a/src/objmesh.cc b/src/objmesh.cc index 3076e67..ab741f4 100644 --- a/src/objmesh.cc +++ b/src/objmesh.cc @@ -31,3 +31,8 @@ void ObjMesh::draw() const glPopMatrix(); } } + +const AABox &ObjMesh::get_aabox() const +{ + return mesh ? mesh->get_aabbox() : Object::get_aabox(); +} diff --git a/src/objmesh.h b/src/objmesh.h index 811caf6..ece9efe 100644 --- a/src/objmesh.h +++ b/src/objmesh.h @@ -13,6 +13,8 @@ public: ObjType get_type() const; void draw() const; + + const AABox &get_aabox() const; }; #endif // OBJMESH_H_ diff --git a/src/opengl.c b/src/opengl.c new file mode 100644 index 0000000..796144c --- /dev/null +++ b/src/opengl.c @@ -0,0 +1,106 @@ +#include "opengl.h" +#include "logger.h" + + +static void GLAPIENTRY gldebug_logger(GLenum src, GLenum type, GLuint id, GLenum severity, + GLsizei len, const char *msg, const void *cls); + +static const char *gldebug_srcstr(unsigned int src); +static const char *gldebug_typestr(unsigned int type); +static const char *gldebug_sevstr(unsigned int sev); + +struct GLCaps glcaps; + +int init_opengl(void) +{ + glewInit(); + + glcaps.debug = GLEW_ARB_debug_output; + +#ifndef NDEBUG + if(glcaps.debug) { + info_log("Installing OpenGL debug callback\n"); + glDebugMessageCallback(gldebug_logger, 0); + } +#endif + + return 0; +} + + +static void GLAPIENTRY gldebug_logger(GLenum src, GLenum type, GLuint id, GLenum severity, + GLsizei len, const char *msg, const void *cls) +{ + static const char *fmt = "[GLDEBUG] (%s) %s: %s\n"; + switch(type) { + case GL_DEBUG_TYPE_ERROR: + error_log(fmt, gldebug_srcstr(src), gldebug_typestr(type), msg); + break; + + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: + case GL_DEBUG_TYPE_PORTABILITY: + case GL_DEBUG_TYPE_PERFORMANCE: + warning_log(fmt, gldebug_srcstr(src), gldebug_typestr(type), msg); + break; + + default: + debug_log(fmt, gldebug_srcstr(src), gldebug_typestr(type), msg); + } +} + +static const char *gldebug_srcstr(unsigned int src) +{ + switch(src) { + case GL_DEBUG_SOURCE_API: + return "api"; + case GL_DEBUG_SOURCE_WINDOW_SYSTEM: + return "wsys"; + case GL_DEBUG_SOURCE_SHADER_COMPILER: + return "sdrc"; + case GL_DEBUG_SOURCE_THIRD_PARTY: + return "3rdparty"; + case GL_DEBUG_SOURCE_APPLICATION: + return "app"; + case GL_DEBUG_SOURCE_OTHER: + return "other"; + default: + break; + } + return "unknown"; +} + +static const char *gldebug_typestr(unsigned int type) +{ + switch(type) { + case GL_DEBUG_TYPE_ERROR: + return "error"; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: + return "deprecated"; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: + return "undefined behavior"; + case GL_DEBUG_TYPE_PORTABILITY: + return "portability warning"; + case GL_DEBUG_TYPE_PERFORMANCE: + return "performance warning"; + case GL_DEBUG_TYPE_OTHER: + return "other"; + default: + break; + } + return "unknown"; +} + +static const char *gldebug_sevstr(unsigned int sev) +{ + switch(sev) { + case GL_DEBUG_SEVERITY_HIGH: + return "high"; + case GL_DEBUG_SEVERITY_MEDIUM: + return "medium"; + case GL_DEBUG_SEVERITY_LOW: + return "low"; + default: + break; + } + return "unknown"; +} diff --git a/src/opengl.h b/src/opengl.h index deeddd3..6ef1733 100644 --- a/src/opengl.h +++ b/src/opengl.h @@ -3,4 +3,20 @@ #include -#endif // OPENGL_H_ +struct GLCaps { + int debug; /* ARB_debug_output */ +}; + +extern struct GLCaps glcaps; + +#ifdef __cplusplus +extern "C" { +#endif + +int init_opengl(void); + +#ifdef __cplusplus +} +#endif + +#endif /* OPENGL_H_ */ diff --git a/src/post.cc b/src/post.cc index b675f5b..885a212 100644 --- a/src/post.cc +++ b/src/post.cc @@ -1,5 +1,6 @@ #include "opengl.h" #include "app.h" +#include "texture.h" // next_pow2 static void update_fbtex(); @@ -36,17 +37,6 @@ void slow_post(unsigned int sdr) glPopAttrib(); } -static int next_pow2(int x) -{ - x--; - x = (x >> 1) | x; - x = (x >> 2) | x; - x = (x >> 4) | x; - x = (x >> 8) | x; - x = (x >> 16) | x; - return x + 1; -} - static void update_fbtex() { if(win_width <= fb_tex_width && win_height <= fb_tex_height) { diff --git a/src/renderer.cc b/src/renderer.cc index 0987b0d..bb274f2 100644 --- a/src/renderer.cc +++ b/src/renderer.cc @@ -45,5 +45,17 @@ void Renderer::draw() const void Renderer::draw_object(Object *obj) const { - obj->draw(); + bool vis = true; + SceneNode *n = obj->node; + while(n) { + if(!n->visible) { + vis = false; + break; + } + n = n->get_parent(); + } + + if(vis) { + obj->draw(); + } } diff --git a/src/rtarg.cc b/src/rtarg.cc new file mode 100644 index 0000000..12a4ddb --- /dev/null +++ b/src/rtarg.cc @@ -0,0 +1,326 @@ +#include +#include "rtarg.h" + +struct RTStackItem { + RenderTarget *rt; + int saved_vp[4]; +}; + +static void attach_depth_texture(Texture *tex); + +#define MAX_STACK_SIZE 16 +static RTStackItem rstack[MAX_STACK_SIZE]; +static int rtop; + +void set_render_target(RenderTarget *rt) +{ + if(rt) { + if(!rstack[rtop].rt) { + glGetIntegerv(GL_VIEWPORT, rstack[rtop].saved_vp); + } + rt->bind(); + + } else { + int *vp = rstack[rtop].saved_vp; + glViewport(vp[0], vp[1], vp[2], vp[3]); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + + rstack[rtop].rt = rt; +} + +RenderTarget *current_render_target() +{ + return rstack[rtop].rt; +} + +bool push_render_target(RenderTarget *rt) +{ + if(!rt) { + error_log("push_render_target(0) is invalid\n"); + return false; + } + if(rtop >= MAX_STACK_SIZE - 1) { + warning_log("push_render_target: overflow\n"); + return false; + } + int *vp = rstack[rtop + 1].saved_vp; + RenderTarget *prev = current_render_target(); + + if(prev) { + vp[0] = vp[1] = 0; + vp[2] = prev->get_width(); + vp[3] = prev->get_height(); + } else { + glGetIntegerv(GL_VIEWPORT, vp); + } + rstack[++rtop].rt = rt; + + rt->bind(); + return true; +} + +bool pop_render_target() +{ + if(rtop <= 0) { + error_log("pop_render_target: undeflow\n"); + return false; + } + + int *vp = rstack[rtop].saved_vp; + glViewport(vp[0], vp[1], vp[2], vp[3]); + + if(rstack[--rtop].rt) { + rstack[rtop].rt->bind(); + } else { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + return true; +} + +RenderTarget::RenderTarget() +{ + fbo = 0; + rbdepth = 0; + rbdepth_fmt = 0; + width = height = tex_width = tex_height = 0; + auto_vport = true; + rtcount = 0; + + for(int i=0; i<4; i++) { + tex[i] = 0; + own_texture[i] = true; + } + depth = 0; +} + +RenderTarget::~RenderTarget() +{ + destroy(); +} + +bool RenderTarget::create(int xsz, int ysz, unsigned int fmt, unsigned int flags) +{ + return create_mrt(xsz, ysz, 1, fmt, flags); +} + +bool RenderTarget::create_mrt(int xsz, int ysz, int num, unsigned int fmt, unsigned int flags) +{ + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + width = xsz; + height = ysz; + tex_width = next_pow2(xsz); + tex_height = next_pow2(ysz); + rtcount = num; + + texmat.scaling((float)width / (float)tex_width, (float)height / (float)tex_height, 1); + + if(flags & RT_COLOR) { + for(int i=0; icreate(tex_width, tex_height, TEX_2D, fmt); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, + tex[i]->get_id(), 0); + glBindTexture(GL_TEXTURE_2D, 0); + own_texture[i] = true; + } + } else { + // in case set_texture was called before create + for(int i=0; iget_id(), 0); + } + } + } + + if(flags & (RT_DEPTH | RT_STENCIL)) { + unsigned int attachment; + + glGenRenderbuffers(1, &rbdepth); + glBindRenderbuffer(GL_RENDERBUFFER, rbdepth); + + switch(flags & (RT_DEPTH | RT_STENCIL)) { + case RT_DEPTH: + rbdepth_fmt = GL_DEPTH_COMPONENT24; + attachment = GL_DEPTH_ATTACHMENT; + break; + + case RT_STENCIL: + rbdepth_fmt = GL_STENCIL_INDEX8; + attachment = GL_STENCIL_ATTACHMENT; + break; + + case RT_DEPTH | RT_STENCIL: + rbdepth_fmt = GL_DEPTH24_STENCIL8; + attachment = GL_DEPTH_STENCIL_ATTACHMENT; + break; + } + + glRenderbufferStorage(GL_RENDERBUFFER, rbdepth_fmt, width, height); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, rbdepth); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + } else if(depth) { + attach_depth_texture(depth); + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + return true; +} + +void RenderTarget::destroy() +{ + for(int i=0; i<4; i++) { + if(tex[i] && own_texture[i]) { + delete tex[i]; + tex[i] = 0; + } + } + if(rbdepth) { + glDeleteRenderbuffers(1, &rbdepth); + rbdepth = 0; + } + if(fbo) { + glDeleteFramebuffers(1, &fbo); + fbo = 0; + } +} + +bool RenderTarget::resize(int xsz, int ysz) +{ + int new_tx = next_pow2(xsz); + int new_ty = next_pow2(ysz); + + if(new_tx != tex_width || new_ty != tex_height) { + tex_width = new_tx; + tex_height = new_ty; + + for(int i=0; i<4; i++) { + if(tex[i]) { + tex[i]->create(new_tx, new_ty, TEX_2D, tex[i]->get_format()); + } + } + + if(depth) { + depth->create(new_tx, new_ty, TEX_2D, depth->get_format()); + } + } + + if(rbdepth) { + glBindRenderbuffer(GL_RENDERBUFFER, rbdepth); + glRenderbufferStorage(GL_RENDERBUFFER, rbdepth_fmt, xsz, ysz); + } + + width = xsz; + height = ysz; + return true; +} + +int RenderTarget::get_width() const +{ + return width; +} + +int RenderTarget::get_height() const +{ + return height; +} + +void RenderTarget::set_texture(Texture *tex, int idx) +{ + if(this->tex[idx] && own_texture[idx]) { + delete this->tex[idx]; + } + this->tex[idx] = tex; + own_texture[idx] = false; + + if(fbo) { + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + idx, GL_TEXTURE_2D, + tex->get_id(), 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } +} + +void RenderTarget::set_depth_texture(Texture *tex) +{ + depth = tex; + + if(fbo) { + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + attach_depth_texture(tex); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } +} + +Texture *RenderTarget::texture(int idx) const +{ + return tex[idx]; +} + +Texture *RenderTarget::depth_texture() const +{ + return depth; +} + +void RenderTarget::set_auto_viewport(bool enable) +{ + auto_vport = enable; +} + +bool RenderTarget::auto_viewport() const +{ + return auto_vport; +} + +void RenderTarget::bind() const +{ + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + if(auto_vport) { + glViewport(0, 0, width, height); + } +} + +const Mat4 &RenderTarget::texture_matrix() const +{ + return texmat; +} + +static void attach_depth_texture(Texture *tex) +{ + unsigned int fmt = tex->get_format(); + unsigned int attachment = 0; + + switch(fmt) { + case GL_DEPTH_COMPONENT: + case GL_DEPTH_COMPONENT16: + case GL_DEPTH_COMPONENT24: + case GL_DEPTH_COMPONENT32: + attachment = GL_DEPTH_ATTACHMENT; + break; + + case GL_STENCIL_INDEX: + case GL_STENCIL_INDEX1: + case GL_STENCIL_INDEX4: + case GL_STENCIL_INDEX8: + attachment = GL_STENCIL_ATTACHMENT; + break; + + case GL_DEPTH_STENCIL: + case GL_DEPTH24_STENCIL8: + case GL_DEPTH32F_STENCIL8: + case GL_UNSIGNED_INT_24_8: + attachment = GL_DEPTH_STENCIL_ATTACHMENT; + break; + + default: + error_log("failed to attach depth/stencil texture: unexpected texture format: %x\n", fmt); + return; + } + + glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, tex->get_id(), 0); +} diff --git a/src/rtarg.h b/src/rtarg.h new file mode 100644 index 0000000..2cead11 --- /dev/null +++ b/src/rtarg.h @@ -0,0 +1,65 @@ +#ifndef RTARG_H_ +#define RTARG_H_ + +#include +#include "opengl.h" +#include "texture.h" + +// flags for RenderTarget::create +enum { + RT_COLOR = 1, + RT_DEPTH = 2, + RT_STENCIL = 4 +}; + +class RenderTarget { +private: + int width, height; + int tex_width, tex_height; + + unsigned int fbo; + Texture *tex[4]; + int rtcount; + // either the depth texture or a dummy depth render target is used + Texture *depth; + unsigned int rbdepth, rbdepth_fmt; + Mat4 texmat; // texture matrix to map tex coords from [0,1] to the useful area + + bool own_texture[4]; + bool auto_vport; + +public: + RenderTarget(); + ~RenderTarget(); + + bool create(int xsz, int ysz, unsigned int fmt = GL_SRGB_ALPHA, + unsigned int flags = RT_COLOR | RT_DEPTH); + bool create_mrt(int xsz, int ysz, int num, unsigned int fmt = GL_RGB16F, + unsigned int flags = RT_COLOR | RT_DEPTH); + void destroy(); + + bool resize(int xsz, int ysz); + + int get_width() const; + int get_height() const; + + void set_texture(Texture *tex, int idx = 0); + void set_depth_texture(Texture *tex); + + Texture *texture(int idx = 0) const; + Texture *depth_texture() const; + + void set_auto_viewport(bool enable); + bool auto_viewport() const; + + void bind() const; + const Mat4 &texture_matrix() const; +}; + +void set_render_target(RenderTarget *rt); +RenderTarget *current_render_target(); + +bool push_render_target(RenderTarget *rt); +bool pop_render_target(); + +#endif // RTARG_H_ diff --git a/src/scene.cc b/src/scene.cc index 012847c..b439286 100644 --- a/src/scene.cc +++ b/src/scene.cc @@ -203,11 +203,10 @@ static SceneNode *find_node_rec(SceneNode *tree, const std::regex &re) if(std::regex_match(tree->get_name(), re)) { return tree; } - debug_log("no match: \"%s\"\n", tree->get_name()); int num = tree->get_num_children(); for(int i=0; iget_child(i), re); if(n) return n; } return 0; diff --git a/src/snode.cc b/src/snode.cc index a1d41ae..b171f35 100644 --- a/src/snode.cc +++ b/src/snode.cc @@ -11,6 +11,8 @@ SceneNode::SceneNode() scene = 0; parent = 0; name = 0; + visible = true; + local_bvol_valid = false; } SceneNode::SceneNode(Object *obj) @@ -19,6 +21,8 @@ SceneNode::SceneNode(Object *obj) scene = 0; parent = 0; name = 0; + visible = true; + local_bvol_valid = false; add_object(obj); } @@ -95,6 +99,8 @@ void SceneNode::add_object(Object *obj) this->obj.push_back(obj); obj->node = this; + + local_bvol_valid = false; } bool SceneNode::remove_object(Object *o) @@ -109,6 +115,8 @@ bool SceneNode::remove_object(Object *o) return false; } obj.erase(it); + + local_bvol_valid = false; return true; } @@ -199,8 +207,21 @@ void SceneNode::update(float dt) if(debug_gui) { if(parent_expanded) { - int flags = children.empty() ? ImGuiTreeNodeFlags_Leaf : 0; + ImGui::PushID(this); + ImGui::AlignTextToFramePadding(); + + int flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; + if(children.empty()) flags |= ImGuiTreeNodeFlags_Leaf; + if(dbg_sel_node == this) flags |= ImGuiTreeNodeFlags_Selected; expanded = ImGui::TreeNodeEx(name ? name : "", flags); + if(ImGui::IsItemClicked()) { + dbg_sel_node = this; + } + + ImGui::NextColumn(); + ImGui::Checkbox("##vis", &visible); + ImGui::NextColumn(); + ImGui::PopID(); } } @@ -276,3 +297,55 @@ bool SceneNode::intersect(const Ray &ray, HitPoint *hit) const } return false; } + +const AABox &SceneNode::calc_local_bounds() +{ + local_bvol = AABox(Vec3(FLT_MAX, FLT_MAX, FLT_MAX), Vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX)); + + // calculate the axis-aligned bounding box of all objects in this node + int nobj = obj.size(); + for(int i=0; iget_aabox(); + calc_bounding_aabox(&local_bvol, &local_bvol, &tmp); + } + + local_bvol_valid = true; + return local_bvol; +} + +const AABox &SceneNode::get_local_bounds() const +{ + if(!local_bvol_valid) { + ((SceneNode*)this)->calc_local_bounds(); + } + return local_bvol; +} + +AABox SceneNode::get_node_bounds() const +{ + get_local_bounds(); // validate local_bvol + + // calculate the transformed local_bvol + Box node_bbox = Box(local_bvol, xform); + + // then calculate the axis-aligned bounding box + AABox aabox; + calc_bounding_aabox(&aabox, &node_bbox); + return aabox; +} + +AABox SceneNode::get_bounds() const +{ + AABox sub_aabb = AABox(Vec3(FLT_MAX, FLT_MAX, FLT_MAX), Vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX)); + + // calculate the bounding box of all children + int nchild = children.size(); + for(int i=0; iget_bounds(); + calc_bounding_aabox(&sub_aabb, &sub_aabb, &tmp); + } + + AABox aabb = get_node_bounds(); + calc_bounding_aabox(&aabb, &aabb, &sub_aabb); + return aabb; +} diff --git a/src/snode.h b/src/snode.h index 5af02f4..6fa52ba 100644 --- a/src/snode.h +++ b/src/snode.h @@ -3,6 +3,7 @@ #include #include "object.h" +#include "geom.h" #include "gmath/gmath.h" class Scene; @@ -23,9 +24,13 @@ private: Mat4 xform; Mat4 inv_xform; + mutable bool local_bvol_valid; + mutable AABox local_bvol; + public: Scene *scene; // scene to which this node belongs Mat4 dbg_xform; + bool visible; // if true, objects of this node are supposed to be visible SceneNode(); explicit SceneNode(Object *obj); @@ -69,6 +74,15 @@ public: void apply_xform(); bool intersect(const Ray &ray, HitPoint *hit) const; + + // cached local bounding box (all objects in this node in model space) + const AABox &calc_local_bounds(); + const AABox &get_local_bounds() const; + + // world bounding box of the node + AABox get_node_bounds() const; + // world bounding box of the node and it's subtree + AABox get_bounds() const; }; #endif // SNODE_H_ diff --git a/src/texture.cc b/src/texture.cc index f4f7cfe..3af8116 100644 --- a/src/texture.cc +++ b/src/texture.cc @@ -43,6 +43,17 @@ void bind_texture(Texture *tex, int tunit) } } +int next_pow2(int x) +{ + x--; + x = (x >> 1) | x; + x = (x >> 2) | x; + x = (x >> 4) | x; + x = (x >> 8) | x; + x = (x >> 16) | x; + return x + 1; +} + Image *Texture::default_img; @@ -174,7 +185,7 @@ void Texture::create(int xsz, int ysz, TextureType textype, unsigned int ifmt) glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - switch(type) { + switch(textype) { case TEX_2D: glTexImage2D(GL_TEXTURE_2D, 0, glifmt_from_ifmt(ifmt), xsz, ysz, 0, fmt, type, 0); break; diff --git a/src/texture.h b/src/texture.h index 5c4bf3c..41749f8 100644 --- a/src/texture.h +++ b/src/texture.h @@ -60,6 +60,7 @@ public: }; void bind_texture(Texture *tex, int tunit = 0); +int next_pow2(int x); class TextureSet : public DataSet { private: diff --git a/src/ui.cc b/src/ui.cc index a0babe4..78c7ece 100644 --- a/src/ui.cc +++ b/src/ui.cc @@ -29,7 +29,8 @@ static Text *txlist; static long timeout = 2000; static long trans_time = 250; -static dtx_font *font; +dtx_font *ui_font; +int ui_font_size = FONTSZ; void set_message_timeout(long tm) { @@ -108,7 +109,7 @@ void print_textv(const Vec2 &pos, const Vec3 &color, const char *fmt, va_list ap void draw_ui() { - if(!font) return; + if(!ui_font) return; while(msglist && msglist->show_until <= time_msec) { Message *msg = msglist; @@ -117,7 +118,7 @@ void draw_ui() delete msg; } - dtx_use_font(font, FONTSZ); + dtx_use_font(ui_font, ui_font_size); glMatrixMode(GL_PROJECTION); glPushMatrix(); @@ -176,10 +177,10 @@ static bool init() done_init = true; - if(!(font = dtx_open_font("data/ui.font", 0))) { + if(!(ui_font = dtx_open_font("data/ui.font", 0))) { fprintf(stderr, "failed to open font: data/ui.font\n"); return false; } - dtx_prepare_range(font, FONTSZ, 32, 127); + dtx_prepare_range(ui_font, ui_font_size, 32, 127); return true; } diff --git a/src/ui.h b/src/ui.h index 088fb82..ed229cb 100644 --- a/src/ui.h +++ b/src/ui.h @@ -4,6 +4,10 @@ #include #include +struct dtx_font; +extern dtx_font *ui_font; +extern int ui_font_size; + void set_message_timeout(long timeout); void show_message(const char *fmt, ...); void show_message(long timeout, const Vec3 &color, const char *fmt, ...); diff --git a/src/ui_exhibit.cc b/src/ui_exhibit.cc new file mode 100644 index 0000000..87fea01 --- /dev/null +++ b/src/ui_exhibit.cc @@ -0,0 +1,450 @@ +#include +#if defined(WIN32) || defined(__WIN32__) +#include +#else +#include +#endif +#include +#include "ui_exhibit.h" +#include "ui.h" +#include "app.h" +#include "snode.h" +#include "sdr.h" +#include "rtarg.h" + +struct Rect { + float x, y, w, h; +}; + +static void draw_frame(const Rect &rect); +static void draw_titlebar(const Rect &rect); +static void draw_tabs(const Rect &rect); +static void draw_text(const Rect &rect); +static void layout_text(const char *text); + +static struct dtx_font *font; +static int font_size; +static unsigned int fontsdr; + +static float aspect; +static int ui_width, ui_height; + +static RenderTarget *rtarg; +static const SceneNode *parent; +static Vec3 pos; +static Vec2 size; +static int text_padding; +static float text_scale = 0.65f; +static Exhibit *ex; +static int vis_tab, num_tabs; +static std::vector tab_names; +static float scroll; +static std::vector text_lines; +static int max_line_size; +static AudioStream *voice; + +enum {COL_BG, COL_FG, COL_FRM}; +static float color[][3] = { + {0.014, 0.016, 0.04}, // COL_BG + {0.31, 0.58, 0.9}, // COL_FG + {0.19, 0.23, 0.46} // COL_FRM +}; + + +bool exui_init() +{ + if(!(font = dtx_open_font_glyphmap("data/ui_en.glyphmap"))) { + error_log("failed to open exhibit ui font\n"); + return false; + } + font_size = dtx_get_glyphmap_ptsize(dtx_get_glyphmap(font, 0)); + + if(!(fontsdr = create_program_load("sdr/dfont.v.glsl", "sdr/dfont.p.glsl"))) { + error_log("failed to load font shader\n"); + return false; + } + + size.x = 15; + size.y = 18; + text_padding = 6; + + aspect = size.x / size.y; + ui_height = 512; + ui_width = ui_height * aspect; + + rtarg = new RenderTarget; + if(!rtarg->create(ui_width, ui_height, GL_RGBA)) { + error_log("failed to create exui render target\n"); + return false; + } + + return true; +} + +void exui_shutdown() +{ + dtx_close_font(font); + delete rtarg; +} + +void exui_setnode(const SceneNode *node) +{ + parent = node; +} + +void exui_change_tab(int dir) +{ + vis_tab = (vis_tab + dir) % num_tabs; +} + +void exui_scroll(float delta) +{ +} + +bool exui_raycast(const Ray &ray) +{ + return false; +} + +void exui_update(float dt) +{ + if(exsel_active.ex != ex) { + ex = exsel_active.ex; + scroll = 0.0f; + vis_tab = 0; + num_tabs = 0; + tab_names.clear(); + text_lines.clear(); + if(voice) voice->stop(); + + if(ex) { + int num_data = ex->data.size(); + for(int i=0; idata[i].type == EXDATA_INFO) { + layout_text(ex->data[i].text.c_str()); + voice = ex->data[i].voice; + ++num_tabs; + tab_names.push_back("info"); + } + } + + if(voice) { + voice->play(AUDIO_PLAYMODE_ONCE); + } + } else { + voice = 0; + } + } +} + +static void draw_2d_ui() +{ + dtx_use_font(font, font_size); + float rowspc = dtx_line_height() * text_scale; + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glTranslatef(-1, 1, 0); + glScalef(2.0 / ui_width, -2.0 / ui_height, 1); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + glUseProgram(0); + + glPushAttrib(GL_ENABLE_BIT); + glDisable(GL_TEXTURE_2D); + glDisable(GL_LIGHTING); + glDisable(GL_DEPTH_TEST); + glEnable(GL_SCISSOR_TEST); + + Rect rect = {0, 0, (float)ui_width, (float)ui_height}; + draw_frame(rect); + Rect tbar_rect = {rect.x, rect.y, rect.w, rowspc + text_padding}; // half the padding + draw_titlebar(tbar_rect); + Rect tabs_rect = {tbar_rect.x, tbar_rect.y + tbar_rect.h, tbar_rect.w, tbar_rect.h}; + draw_tabs(tabs_rect); + + if(num_tabs) { + switch(ex->data[vis_tab].type) { + case EXDATA_INFO: + { + Rect text_rect = {rect.x, tabs_rect.y + tabs_rect.h, rect.w, rect.h - tabs_rect.y - tabs_rect.h}; + draw_text(text_rect); + } + break; + + default: + break; + } + } + + glPopAttrib(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); +} + +void exui_draw() +{ + if(!exsel_active) return; + if(!font) return; + + // render the 2D UI in a texture + push_render_target(rtarg); + glClearColor(0, 1, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); + draw_2d_ui(); + pop_render_target(); + + // place UI image into the scene + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + Mat4 mvmat; + glGetFloatv(GL_MODELVIEW_MATRIX, mvmat[0]); + if(parent) { + mvmat = parent->get_matrix() * mvmat; + } + mvmat.translate(pos.x, pos.y, pos.z); + + mvmat[0][0] = mvmat[1][1] = mvmat[2][2] = 1.0f; + mvmat[0][1] = mvmat[0][2] = mvmat[1][0] = mvmat[2][0] = mvmat[1][2] = mvmat[2][1] = 0.0f; + glLoadMatrixf(mvmat[0]); + + glPushAttrib(GL_ENABLE_BIT); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glEnable(GL_TEXTURE_2D); + glDepthMask(0); + + glUseProgram(0); + bind_texture(rtarg->texture()); + + glMatrixMode(GL_TEXTURE); + glLoadMatrixf(rtarg->texture_matrix()[0]); + + glBegin(GL_QUADS); + glColor3f(1, 1, 1); + glTexCoord2f(0, 0); glVertex2f(-size.x / 2, -size.y / 2); + glTexCoord2f(1, 0); glVertex2f(size.x / 2, -size.y / 2); + glTexCoord2f(1, 1); glVertex2f(size.x / 2, size.y / 2); + glTexCoord2f(0, 1); glVertex2f(-size.x / 2, size.y / 2); + glEnd(); + + glLoadIdentity(); + + glDepthMask(1); + glPopAttrib(); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); +} + +static inline float *vrect(const Rect &rect, int i) +{ + static float v[2]; + v[0] = ((i + 1) & 2) ? rect.x + rect.w : rect.x; + v[1] = (i & 2) ? rect.y : rect.y + rect.h; + return v; +} + +static inline void draw_rect(const Rect &rect, int col) +{ + glBegin(GL_QUADS); + glColor3fv(color[col]); + for(int i=0; i<4; i++) + glVertex2fv(vrect(rect, i)); + glEnd(); +} + +static void clip_rect(const Rect &rect) +{ + glScissor(rect.x, ui_height - rect.y - rect.h, rect.w, rect.h); +} + +static void draw_frame(const Rect &rect) +{ + clip_rect(rect); + + draw_rect(rect, COL_BG); + glLineWidth(3.0); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + draw_rect(rect, COL_FRM); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +} + +static void draw_titlebar(const Rect &rect) +{ + clip_rect(rect); + + draw_rect(rect, COL_FRM); + + const char *title = ex->get_name(); + if(title) { + glUseProgram(fontsdr); + + glPushMatrix(); + glTranslatef(rect.x + text_padding, rect.y + rect.h - text_padding, 0); + glScalef(text_scale, -text_scale, text_scale); + + glColor3fv(color[COL_BG]); + dtx_string(ex->get_name()); + glPopMatrix(); + + glUseProgram(0); + } +} + +static void draw_tabs(const Rect &rect) +{ + if(!num_tabs) return; + + clip_rect(rect); + + glLineWidth(1); + if(num_tabs == 1) { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + draw_rect(rect, COL_FRM); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } + + int max_tab_size = ui_width / 2; + int tab_size = std::min(max_tab_size, ui_width / num_tabs); + + for(int i=0; i right_margin) { + if(text == text_lines.back()) { + // not even a single character fits on a line... abort + warning_log("text layout failed. glyph %d doesn't fit in line (%d)\n", code, right_margin - left_margin); + text_lines.clear(); + return; + } + if(last_break) { + text_lines.push_back(last_break + 1); + last_break = 0; + } else { + // no good point to break, just break here + text_lines.push_back(text); + } + + int d = text_lines.back() - (text_lines[text_lines.size() - 2]); + if(d > max_line_size) max_line_size = d; + } + text = next; + } + text_lines.push_back(0); + + /* + debug_log("text layout:\n"); + for(size_t i=0; i + +class SceneNode; + +bool exui_init(); +void exui_shutdown(); +void exui_setnode(const SceneNode *node); + +void exui_change_tab(int dir); +void exui_scroll(float delta); + +bool exui_raytest(const Ray &ray); + +void exui_update(float dt); +void exui_draw(); + +#endif // UI_EXHIBIT_H_ diff --git a/tools/prepare_data b/tools/prepare_data index 7106581..a7f1eb4 100755 --- a/tools/prepare_data +++ b/tools/prepare_data @@ -63,7 +63,7 @@ while read line; do touch "$infile" fi - if [ "$infile" -nt "$outfile" ]; then + if [ \( ! -f "$outfile" \) -o \( "$infile" -nt "$outfile" \) ]; then if [ "$op" = nop ]; then echo copying $fname mkdir -p $outdir/$(dirname $path)