Merge branch 'master' of /home/nuclear/code/laserbrain/demo
authorJohn Tsiombikas <nuclear@member.fsf.org>
Sun, 21 Jan 2018 15:26:49 +0000 (17:26 +0200)
committerJohn Tsiombikas <nuclear@member.fsf.org>
Sun, 21 Jan 2018 15:26:49 +0000 (17:26 +0200)
45 files changed:
Makefile
sdr/dfont.p.glsl [new file with mode: 0644]
sdr/dfont.v.glsl [new file with mode: 0644]
src/app.cc
src/app.h
src/avatar.h
src/blob_exhibit.cc
src/blob_exhibit.h
src/blobs/metasurf.c
src/blobs/metasurf.h
src/dbg_gui.cc
src/dbg_gui.h
src/exhibit.cc
src/exhibit.h
src/exman.cc
src/exman.h
src/geom.cc
src/geom.h
src/geomdraw.cc [new file with mode: 0644]
src/geomdraw.h [new file with mode: 0644]
src/logger.cc
src/logger.h
src/main.cc
src/mesh.h
src/metascene.cc
src/object.cc
src/object.h
src/objmesh.cc
src/objmesh.h
src/opengl.c [new file with mode: 0644]
src/opengl.h
src/post.cc
src/renderer.cc
src/rtarg.cc [new file with mode: 0644]
src/rtarg.h [new file with mode: 0644]
src/scene.cc
src/snode.cc
src/snode.h
src/texture.cc
src/texture.h
src/ui.cc
src/ui.h
src/ui_exhibit.cc [new file with mode: 0644]
src/ui_exhibit.h [new file with mode: 0644]
tools/prepare_data

index dcdb394..b2011c5 100644 (file)
--- 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 (file)
index 0000000..dad3080
--- /dev/null
@@ -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 (file)
index 0000000..7aa01e4
--- /dev/null
@@ -0,0 +1,6 @@
+void main()
+{
+       gl_Position = ftransform();
+       gl_TexCoord[0] = gl_MultiTexCoord0;
+       gl_FrontColor = gl_Color;
+}
index d9d44c0..0ac331b 100644 (file)
@@ -1,4 +1,5 @@
 #include <stdio.h>
+#include <limits.h>
 #include <assert.h>
 #include <goatvr.h>
 #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;
+}
index 83680ec..686c7e0 100644 (file)
--- 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,
index ca3ea8c..6c3aa41 100644 (file)
@@ -1,6 +1,8 @@
 #ifndef AVATAR_H_
 #define AVATAR_H_
 
+// TODO incomplete
+
 #include <gmath/gmath.h>
 
 /* when head-tracking is available, head_tilt is ignored, and the
index 5e9f213..cd29e96 100644 (file)
@@ -1,6 +1,7 @@
 #include "blob_exhibit.h"
 #include "blobs/metasurf.h"
 #include "app.h"
+#include <imago2.h>
 
 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; i<NUM_MBALLS; i++) {
                priv->mballs[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; i<NUM_MBALLS; i++) {
                float t = fmod(sec * priv->mballs[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; i<zres; i++) {
+               float z = zmin + i * zstep;
+               float *voxptr = msurf_slice(priv->msurf, i);
+
+               for(int j=0; j<yres; j++) {
+                       float y = ymin + j * ystep;
+
+                       for(int k=0; k<xres; k++) {
+                               float x = xmin + k * xstep;
+
+                               float sum = 0.0f;
+                               for(int n=0; n<NUM_MBALLS; n++) {
+                                       float dx = x - priv->mballs[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; i<NUM_MBALLS; i++) {
-               float dx = x - priv->mballs[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; i<nverts; i++) {
+               glVertex3f(varr[0], varr[1], varr[2]);
+               glVertex3f(varr[0] + narr[0] * nscale, varr[1] + narr[1] * nscale, varr[2] + narr[2] * nscale);
+               varr += 3;
+               narr += 3;
+       }
+       glEnd();
+       */
 
-               sum += priv->mballs[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;
 }
index 001047a..be279e6 100644 (file)
@@ -18,6 +18,8 @@ public:
 
        void update(float dt);
        void draw() const;
+
+       const AABox &get_aabox() const;
 };
 
 #endif // BLOB_EXHIBIT_H_
index 2207b3d..31efcf4 100644 (file)
@@ -1,60 +1,28 @@
-/*
-metasurf - a library for implicit surface polygonization
-Copyright (C) 2011-2016  John Tsiombikas <nuclear@member.fsf.org>
-
-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 <http://www.gnu.org/licenses/>.
-*/
-/* this is pulled from: https://github.com/jtsiomb/metasurf */
 #include <stdio.h>
 #include <stdlib.h>
+#include <math.h>
 #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; i<ms->res[0] - 1; i++) {
-               pos[1] = ms->min[1];
                for(j=0; j<ms->res[1] - 1; j++) {
-
-                       pos[2] = ms->min[2];
                        for(k=0; k<ms->res[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 */
index 77d2920..3f7ed6e 100644 (file)
@@ -1,32 +1,13 @@
-/*
-metasurf - a library for implicit surface polygonization
-Copyright (C) 2011-2015  John Tsiombikas <nuclear@member.fsf.org>
-
-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 <http://www.gnu.org/licenses/>.
-*/
-/* 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
 }
index 68832ce..c1abce3 100644 (file)
@@ -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;
index f6ff011..69b68aa 100644 (file)
@@ -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();
index d76b291..2e81854 100644 (file)
@@ -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;
 }
index 63b2898..9b5c753 100644 (file)
@@ -1,12 +1,14 @@
 #ifndef EXHIBIT_H_
 #define EXHIBIT_H_
 
+#include <string>
 #include <gmath/gmath.h>
 #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<ExData> 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_
index d93c2ff..e5cdd9f 100644 (file)
+#include <float.h>
 #include <algorithm>
 #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<num; i++) {
-               delete items[i];
+               delete exslots[i];
        }
-       items.clear();
+       exslots.clear();
+
+       delete own_scn; // this must be the last thing to destroy
 }
 
 void ExhibitManager::add(Exhibit *ex)
@@ -24,6 +134,8 @@ void ExhibitManager::add(Exhibit *ex)
        std::vector<Exhibit*>::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<Exhibit*>::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; i<nitems; i++) {
+               ExSelection sel = items[i]->select(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; i<nslots; i++) {
+               ExhibitSlot *slot = exslots[i];
+               if(!slot->empty()) 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; i<num; i++) {
+               // if the exhibit is not part of a scene graph, first call its
+               // node's update function (otherwise it would have been called recursively earlier)
+               if(items[i]->node && !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; i<num; i++) {
+               if(exsel_hover.ex == items[i]) {
+                       const AABox &bvol = items[i]->get_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;
+}
index d91e374..df3d8bd 100644 (file)
@@ -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<Exhibit*> items;
+       std::vector<Exhibit*> items, stashed;
+       std::vector<ExhibitSlot*> 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_
index 55cfed7..5fc3dce 100644 (file)
@@ -1,53 +1,54 @@
 #include <algorithm>
 #include <float.h>
 #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 &cent, 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; i<num; i++) {
+               if(!calc_bounding_sphere(sph, sph, objv[i])) {
+                       return false;
+               }
+       }
+       return true;
+}
+
+bool calc_bounding_sphere(Sphere *sph, const Vec3 *v, int num, const Mat4 &xform)
+{
+       if(num <= 0) return false;
+
+       sph->center = Vec3(0.0, 0.0, 0.0);
+       for(int i=0; i<num; i++) {
+               sph->center += xform * v[i];
+       }
+       sph->center /= (float)num;
+
+       float rad_sq = 0.0f;
+       for(int i=0; i<num; i++) {
+               Vec3 dir = xform * v[i] - sph->center;
+               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; i<num; i++) {
+               if(!calc_bounding_aabox(box, box, objv[i])) {
+                       return false;
+               }
+       }
+       return true;
+}
+
+bool calc_bounding_aabox(AABox *box, const Vec3 *v, int num, const Mat4 &xform)
+{
+       if(num <= 0) return false;
+
+       box->min = box->max = xform * v[0];
+       for(int i=1; i<num; i++) {
+               Vec3 p = xform * v[i];
+
+               for(int j=0; j<3; j++) {
+                       box->min[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;
+}
index e110193..5db1996 100644 (file)
@@ -1,13 +1,19 @@
 #ifndef GEOMOBJ_H_
 #define GEOMOBJ_H_
 
+/* TODO:
+ * - implement distance functions
+ */
+
 #include <gmath/gmath.h>
 
 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 &center, 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 (file)
index 0000000..6dc25ce
--- /dev/null
@@ -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 (file)
index 0000000..05e86e6
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef GEOMDRAW_H_
+#define GEOMDRAW_H_
+
+#include "geom.h"
+
+void draw_geom_object(const GeomObject *gobj);
+
+#endif
index 015c9bd..254c20d 100644 (file)
 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 +34,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 +161,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 "";
+}
index 978d634..28821cd 100644 (file)
@@ -1,10 +1,14 @@
 #ifndef LOGGER_H_
 #define LOGGER_H_
 
+#include <stdio.h>
+
 #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, ...);
index c497dc1..e3c8a73 100644 (file)
@@ -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;
        }
index 90cd1e7..2e43781 100644 (file)
@@ -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();
index 64946cb..d9f96fb 100644 (file)
@@ -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();
index d850d47..2bc8b79 100644 (file)
@@ -35,3 +35,8 @@ void Object::update(float dt)
 void Object::draw() const
 {
 }
+
+const AABox &Object::get_aabox() const
+{
+       return aabb;
+}
index cc82d1b..3ffec1f 100644 (file)
@@ -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_
index 3076e67..ab741f4 100644 (file)
@@ -31,3 +31,8 @@ void ObjMesh::draw() const
                glPopMatrix();
        }
 }
+
+const AABox &ObjMesh::get_aabox() const
+{
+       return mesh ? mesh->get_aabbox() : Object::get_aabox();
+}
index 811caf6..ece9efe 100644 (file)
@@ -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 (file)
index 0000000..9fe7ea3
--- /dev/null
@@ -0,0 +1,104 @@
+#include "opengl.h"
+#include "logger.h"
+
+static void gldebug_logger(unsigned int src, unsigned int type, unsigned int id,
+               unsigned int severity, int 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 gldebug_logger(unsigned int src, unsigned int type, unsigned int id,
+               unsigned int severity, int 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";
+}
index deeddd3..6ef1733 100644 (file)
@@ -3,4 +3,20 @@
 
 #include <GL/glew.h>
 
-#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_ */
index b675f5b..885a212 100644 (file)
@@ -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) {
index 0987b0d..bb274f2 100644 (file)
@@ -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 (file)
index 0000000..12a4ddb
--- /dev/null
@@ -0,0 +1,326 @@
+#include <assert.h>
+#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; i<num; i++) {
+                       tex[i] = new Texture;
+                       tex[i]->create(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; i<num; i++) {
+                       if(tex[i]) {
+                               glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D,
+                                               tex[i]->get_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 (file)
index 0000000..2cead11
--- /dev/null
@@ -0,0 +1,65 @@
+#ifndef RTARG_H_
+#define RTARG_H_
+
+#include <gmath/gmath.h>
+#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_
index 012847c..b439286 100644 (file)
@@ -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; i<num; i++) {
-               SceneNode *n = find_node_rec(tree, re);
+               SceneNode *n = find_node_rec(tree->get_child(i), re);
                if(n) return n;
        }
        return 0;
index a1d41ae..b171f35 100644 (file)
@@ -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 : "<nameless node>", 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; i<nobj; i++) {
+               AABox tmp = obj[i]->get_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; i<nchild; i++) {
+               AABox tmp = children[i]->get_bounds();
+               calc_bounding_aabox(&sub_aabb, &sub_aabb, &tmp);
+       }
+
+       AABox aabb = get_node_bounds();
+       calc_bounding_aabox(&aabb, &aabb, &sub_aabb);
+       return aabb;
+}
index 5af02f4..6fa52ba 100644 (file)
@@ -3,6 +3,7 @@
 
 #include <vector>
 #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_
index f4f7cfe..3af8116 100644 (file)
@@ -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;
index 5c4bf3c..41749f8 100644 (file)
@@ -60,6 +60,7 @@ public:
 };
 
 void bind_texture(Texture *tex, int tunit = 0);
+int next_pow2(int x);
 
 class TextureSet : public DataSet<Texture*> {
 private:
index a0babe4..78c7ece 100644 (file)
--- 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;
 }
index 088fb82..ed229cb 100644 (file)
--- a/src/ui.h
+++ b/src/ui.h
@@ -4,6 +4,10 @@
 #include <stdarg.h>
 #include <gmath/gmath.h>
 
+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 (file)
index 0000000..87fea01
--- /dev/null
@@ -0,0 +1,450 @@
+#include <assert.h>
+#if defined(WIN32) || defined(__WIN32__)
+#include <malloc.h>
+#else
+#include <alloca.h>
+#endif
+#include <drawtext.h>
+#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<std::string> tab_names;
+static float scroll;
+static std::vector<const char*> 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; i<num_data; i++) {
+                               if(ex->data[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<num_tabs; i++) {
+               Rect tr = {rect.x + i * tab_size, rect.y, (float)tab_size, rect.h};
+
+               clip_rect(tr);
+
+               if(vis_tab == i) {
+                       draw_rect(tr, COL_FRM);
+               } else {
+                       glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+                       draw_rect(tr, COL_FRM);
+                       glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+               }
+
+               glPushMatrix();
+               glTranslatef(tr.x + text_padding, tr.y + tr.h - text_padding, 0);
+               glScalef(text_scale, -text_scale, text_scale);
+
+               glUseProgram(fontsdr);
+               glColor3fv(color[vis_tab == i ? COL_BG : COL_FRM]);
+               dtx_string(tab_names[i].c_str());
+               glUseProgram(0);
+
+               glPopMatrix();
+       }
+}
+
+static void draw_text(const Rect &rect)
+{
+       clip_rect(rect);
+
+       char *buf = (char*)alloca(max_line_size + 1);
+
+       float dy = dtx_line_height();
+
+       glUseProgram(fontsdr);
+
+       glMatrixMode(GL_MODELVIEW);
+       glPushMatrix();
+       glTranslatef(rect.x + text_padding, rect.y + dy + text_padding, 0);
+       glScalef(text_scale, -text_scale, text_scale);
+
+       glColor3fv(color[COL_FG]);
+
+       int nlines = text_lines.size() - 1;
+       for(int i=0; i<nlines; i++) {
+               if(i < nlines - 1) {
+                       int sz = text_lines[i + 1] - text_lines[i];
+                       assert(sz <= max_line_size);
+                       memcpy(buf, text_lines[i], sz);
+                       buf[sz] = 0;
+               } else {
+                       buf = (char*)text_lines[i];
+               }
+
+               dtx_position(0, -dy * i);
+               dtx_string(buf);
+       }
+       dtx_position(0, 0);
+
+       glPopMatrix();
+       glUseProgram(0);
+}
+
+static void layout_text(const char *text)
+{
+       text_lines.clear();
+       if(!text) return;
+       if(!font) return;
+
+       dtx_use_font(font, font_size);
+
+       int left_margin = text_padding;
+       int right_margin = ui_width - text_padding;
+
+       text_lines.push_back(text);
+       const char *last_break = 0;
+       max_line_size = 1;
+
+       while(*text) {
+               if(*text == '\n') {     // paragraph break
+                       text_lines.push_back(text);
+                       text_lines.push_back(++text);
+                       last_break = 0;
+                       continue;
+               }
+
+               int code = dtx_utf8_char_code(text);
+               const char *next = dtx_utf8_next_char((char*)text);
+
+               struct dtx_box box;
+               dtx_substring_box(text_lines.back(), 0, text - text_lines.back(), &box);
+               float pos = left_margin + (box.width + box.x) * text_scale;
+
+               if(code < 256 && isspace(code)) {
+                       last_break = text;
+               }
+
+               if(pos > 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<text_lines.size() - 1; i++) {
+               const char *p = text_lines[i];
+               while(*p && p != text_lines[i + 1]) {
+                       putchar(*p);
+                       ++p;
+               }
+               putchar('\n');
+       }
+       debug_log("---\n");
+       */
+}
diff --git a/src/ui_exhibit.h b/src/ui_exhibit.h
new file mode 100644 (file)
index 0000000..335e23e
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef UI_EXHIBIT_H_
+#define UI_EXHIBIT_H_
+
+#include <gmath/gmath.h>
+
+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_
index 7106581..a7f1eb4 100755 (executable)
@@ -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)