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
--- /dev/null
+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;
+}
--- /dev/null
+void main()
+{
+ gl_Position = ftransform();
+ gl_TexCoord[0] = gl_MultiTexCoord0;
+ gl_FrontColor = gl_Color;
+}
#include <stdio.h>
+#include <limits.h>
#include <assert.h>
#include <goatvr.h>
#include "app.h"
#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
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;
unsigned int sdr_ltmap, sdr_ltmap_notex;
+int fpexcept_enabled;
+
static Avatar avatar;
static float cam_dist = 0.0;
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;
}
glEnable(GL_MULTISAMPLE);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
- glEnable(GL_LIGHTING);
glEnable(GL_NORMALIZE);
if(!init_debug_gui()) {
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()) {
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;
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();
mscn->update(dt);
exman->update(dt);
+ exui_update(dt);
float speed = walk_speed * dt;
Vec3 dir;
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);
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()
ImGui::ShowTestWindow();
}
+ glClearColor(1, 1, 1, 1);
+
if(opt.vr) {
// VR mode
goatvr_draw_start();
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) {
}
*/
+ 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);
glPopAttrib();
}
+ exui_draw();
+
print_text(Vec2(9 * win_width / 10, 20), Vec3(1, 1, 0), "fps: %.1f", framerate);
draw_ui();
}
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;
}
}
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
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)
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);
}
}
static void calc_framerate()
{
- static int ncalc;
+ //static int ncalc;
static int nframes;
static long prev_upd;
++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;
+}
#include "texture.h"
#include "scene.h"
+#include "exhibit.h"
extern long time_msec;
extern int win_width, win_height;
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,
#ifndef AVATAR_H_
#define AVATAR_H_
+// TODO incomplete
+
#include <gmath/gmath.h>
/* when head-tracking is available, head_tilt is ignored, and the
#include "blob_exhibit.h"
#include "blobs/metasurf.h"
#include "app.h"
+#include <imago2.h>
struct Metaball {
Vec3 pos;
};
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()
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;
}
{
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);
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;
}
void update(float dt);
void draw() const;
+
+ const AABox &get_aabox() const;
};
#endif // BLOB_EXHIBIT_H_
-/*
-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)
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:
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)
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)
}
+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];
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;
+ }
}
}
}
}
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 */
-/*
-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" {
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
*/
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
}
static void render_func(ImDrawData *ddat);
bool debug_gui, parent_expanded;
+SceneNode *dbg_sel_node;
static ImGuiIO *io;
static Texture *tex;
#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();
#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;
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;
}
#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 {
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);
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
*/
class Exhibit : public Object {
private:
- ExhibitPriv *priv;
+ SceneNode *orig_parent;
public:
+ ExhibitSlot *prev_slot;
+ std::vector<ExData> data;
+
Exhibit();
virtual ~Exhibit();
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_
+#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)
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);
}
}
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;
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);
+ }
}
}
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;
+}
#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);
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_
#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 ¢, 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
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;
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);
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;
+}
#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;
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 {
Sphere();
Sphere(const Vec3 ¢er, float radius);
- GeomObjectType get_type() const;
-
- void set_union(const GeomObject *obj1, const GeomObject *obj2);
- void set_intersection(const GeomObject *obj1, const GeomObject *obj2);
+ virtual bool valid() const;
+ virtual void invalidate();
- bool intersect(const Ray &ray, HitPoint *hit = 0) const;
- bool contains(const Vec3 &pt) const;
+ virtual bool intersect(const Ray &ray, HitPoint *hit = 0) const;
+ virtual bool contains(const Vec3 &pt) const;
- float distance(const Vec3 &v) const;
+ virtual float distance(const Vec3 &v) const;
+ virtual float signed_distance(const Vec3 &v) const;
};
class AABox : public GeomObject {
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;
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_
--- /dev/null
+#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");
+}
--- /dev/null
+#ifndef GEOMDRAW_H_
+#define GEOMDRAW_H_
+
+#include "geom.h"
+
+void draw_geom_object(const GeomObject *gobj);
+
+#endif
#include <stdio.h>
+#include <stdlib.h>
#include <stdarg.h>
#include "logger.h"
#include "ui.h"
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);
} 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, ...)
}
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 "";
+}
#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, ...);
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;
static bool init(int argc, char **argv)
{
- glewInit();
-
if(!app_init(argc, argv)) {
return false;
}
* @{ */
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();
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();
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);
}
if(debug_gui) {
+ ImGui::Columns(1);
ImGui::End();
}
}
+// XXX not used, renderer draws
void MetaScene::draw() const
{
int nscn = scenes.size();
void Object::draw() const
{
}
+
+const AABox &Object::get_aabox() const
+{
+ return aabb;
+}
enum ObjType { OBJ_NULL, OBJ_MESH };
class Object {
-private:
+protected:
std::string name;
+ mutable AABox aabb;
public:
Material mtl;
- //GeomObject *bvol;
SceneNode *node;
Object();
virtual void update(float dt = 0.0f);
virtual void draw() const;
+
+ virtual const AABox &get_aabox() const;
};
#endif // OBJECT_H_
glPopMatrix();
}
}
+
+const AABox &ObjMesh::get_aabox() const
+{
+ return mesh ? mesh->get_aabbox() : Object::get_aabox();
+}
ObjType get_type() const;
void draw() const;
+
+ const AABox &get_aabox() const;
};
#endif // OBJMESH_H_
--- /dev/null
+#include "opengl.h"
+#include "logger.h"
+
+
+static void GLAPIENTRY gldebug_logger(GLenum src, GLenum type, GLuint id, GLenum severity,
+ GLsizei len, const char *msg, const void *cls);
+
+static const char *gldebug_srcstr(unsigned int src);
+static const char *gldebug_typestr(unsigned int type);
+static const char *gldebug_sevstr(unsigned int sev);
+
+struct GLCaps glcaps;
+
+int init_opengl(void)
+{
+ glewInit();
+
+ glcaps.debug = GLEW_ARB_debug_output;
+
+#ifndef NDEBUG
+ if(glcaps.debug) {
+ info_log("Installing OpenGL debug callback\n");
+ glDebugMessageCallback(gldebug_logger, 0);
+ }
+#endif
+
+ return 0;
+}
+
+
+static void GLAPIENTRY gldebug_logger(GLenum src, GLenum type, GLuint id, GLenum severity,
+ GLsizei len, const char *msg, const void *cls)
+{
+ static const char *fmt = "[GLDEBUG] (%s) %s: %s\n";
+ switch(type) {
+ case GL_DEBUG_TYPE_ERROR:
+ error_log(fmt, gldebug_srcstr(src), gldebug_typestr(type), msg);
+ break;
+
+ case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
+ case GL_DEBUG_TYPE_PORTABILITY:
+ case GL_DEBUG_TYPE_PERFORMANCE:
+ warning_log(fmt, gldebug_srcstr(src), gldebug_typestr(type), msg);
+ break;
+
+ default:
+ debug_log(fmt, gldebug_srcstr(src), gldebug_typestr(type), msg);
+ }
+}
+
+static const char *gldebug_srcstr(unsigned int src)
+{
+ switch(src) {
+ case GL_DEBUG_SOURCE_API:
+ return "api";
+ case GL_DEBUG_SOURCE_WINDOW_SYSTEM:
+ return "wsys";
+ case GL_DEBUG_SOURCE_SHADER_COMPILER:
+ return "sdrc";
+ case GL_DEBUG_SOURCE_THIRD_PARTY:
+ return "3rdparty";
+ case GL_DEBUG_SOURCE_APPLICATION:
+ return "app";
+ case GL_DEBUG_SOURCE_OTHER:
+ return "other";
+ default:
+ break;
+ }
+ return "unknown";
+}
+
+static const char *gldebug_typestr(unsigned int type)
+{
+ switch(type) {
+ case GL_DEBUG_TYPE_ERROR:
+ return "error";
+ case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
+ return "deprecated";
+ case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
+ return "undefined behavior";
+ case GL_DEBUG_TYPE_PORTABILITY:
+ return "portability warning";
+ case GL_DEBUG_TYPE_PERFORMANCE:
+ return "performance warning";
+ case GL_DEBUG_TYPE_OTHER:
+ return "other";
+ default:
+ break;
+ }
+ return "unknown";
+}
+
+static const char *gldebug_sevstr(unsigned int sev)
+{
+ switch(sev) {
+ case GL_DEBUG_SEVERITY_HIGH:
+ return "high";
+ case GL_DEBUG_SEVERITY_MEDIUM:
+ return "medium";
+ case GL_DEBUG_SEVERITY_LOW:
+ return "low";
+ default:
+ break;
+ }
+ return "unknown";
+}
#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_ */
#include "opengl.h"
#include "app.h"
+#include "texture.h" // next_pow2
static void update_fbtex();
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) {
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();
+ }
}
--- /dev/null
+#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);
+}
--- /dev/null
+#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_
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;
scene = 0;
parent = 0;
name = 0;
+ visible = true;
+ local_bvol_valid = false;
}
SceneNode::SceneNode(Object *obj)
scene = 0;
parent = 0;
name = 0;
+ visible = true;
+ local_bvol_valid = false;
add_object(obj);
}
this->obj.push_back(obj);
obj->node = this;
+
+ local_bvol_valid = false;
}
bool SceneNode::remove_object(Object *o)
return false;
}
obj.erase(it);
+
+ local_bvol_valid = false;
return true;
}
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();
}
}
}
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;
+}
#include <vector>
#include "object.h"
+#include "geom.h"
#include "gmath/gmath.h"
class Scene;
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);
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_
}
}
+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;
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;
};
void bind_texture(Texture *tex, int tunit = 0);
+int next_pow2(int x);
class TextureSet : public DataSet<Texture*> {
private:
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)
{
void draw_ui()
{
- if(!font) return;
+ if(!ui_font) return;
while(msglist && msglist->show_until <= time_msec) {
Message *msg = msglist;
delete msg;
}
- dtx_use_font(font, FONTSZ);
+ dtx_use_font(ui_font, ui_font_size);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
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;
}
#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, ...);
--- /dev/null
+#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");
+ */
+}
--- /dev/null
+#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_
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)