initial commit
authorJohn Tsiombikas <nuclear@mutantstargoat.com>
Sat, 10 Dec 2016 23:43:53 +0000 (01:43 +0200)
committerJohn Tsiombikas <nuclear@mutantstargoat.com>
Sat, 10 Dec 2016 23:43:53 +0000 (01:43 +0200)
60 files changed:
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
sdr/post_gamma.p.glsl [new file with mode: 0644]
sdr/post_gamma.v.glsl [new file with mode: 0644]
sdr/shadow-notex.p.glsl [new file with mode: 0644]
sdr/shadow.v.glsl [new file with mode: 0644]
sdr/skydome.p.glsl [new file with mode: 0644]
sdr/skydome.v.glsl [new file with mode: 0644]
src/app.cc [new file with mode: 0644]
src/app.h [new file with mode: 0644]
src/blob_exhibit.cc [new file with mode: 0644]
src/blob_exhibit.h [new file with mode: 0644]
src/blobs/mcubes.h [new file with mode: 0644]
src/blobs/metasurf.c [new file with mode: 0644]
src/blobs/metasurf.h [new file with mode: 0644]
src/datamap.cc [new file with mode: 0644]
src/datamap.h [new file with mode: 0644]
src/dataset.h [new file with mode: 0644]
src/dataset.inl [new file with mode: 0644]
src/exhibit.cc [new file with mode: 0644]
src/exhibit.h [new file with mode: 0644]
src/geom.cc [new file with mode: 0644]
src/geom.h [new file with mode: 0644]
src/image.cc [new file with mode: 0644]
src/image.h [new file with mode: 0644]
src/logger.cc [new file with mode: 0644]
src/logger.h [new file with mode: 0644]
src/main.cc [new file with mode: 0644]
src/material.cc [new file with mode: 0644]
src/material.h [new file with mode: 0644]
src/mech_exhibit.h [new file with mode: 0644]
src/mesh.cc [new file with mode: 0644]
src/mesh.h [new file with mode: 0644]
src/meshgen.cc [new file with mode: 0644]
src/meshgen.h [new file with mode: 0644]
src/metascene.cc [new file with mode: 0644]
src/metascene.h [new file with mode: 0644]
src/object.cc [new file with mode: 0644]
src/object.h [new file with mode: 0644]
src/objmesh.cc [new file with mode: 0644]
src/objmesh.h [new file with mode: 0644]
src/opengl.h [new file with mode: 0644]
src/opt.cc [new file with mode: 0644]
src/opt.h [new file with mode: 0644]
src/post.cc [new file with mode: 0644]
src/post.h [new file with mode: 0644]
src/scene.cc [new file with mode: 0644]
src/scene.h [new file with mode: 0644]
src/sceneload.cc [new file with mode: 0644]
src/sceneload.h [new file with mode: 0644]
src/sdr.c [new file with mode: 0644]
src/sdr.h [new file with mode: 0644]
src/shadow.cc [new file with mode: 0644]
src/shadow.h [new file with mode: 0644]
src/snode.cc [new file with mode: 0644]
src/snode.h [new file with mode: 0644]
src/texture.cc [new file with mode: 0644]
src/texture.h [new file with mode: 0644]
src/ui.cc [new file with mode: 0644]
src/ui.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..9d47d3e
--- /dev/null
@@ -0,0 +1,10 @@
+*.o
+*.swp
+*.d
+oneroom
+data/
+.clang_complete
+*.suo
+*sdf
+Debug/
+Release/
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..ec3a5c3
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,45 @@
+src = $(wildcard src/*.cc) $(wildcard src/blobs/*.cc)
+csrc = $(wildcard src/*.c) $(wildcard src/blobs/*.c)
+obj = $(src:.cc=.o) $(csrc:.c=.o)
+dep = $(obj:.o=.d)
+bin = oneroom
+
+#opt = -O3 -ffast-math
+dbg = -g
+
+incpath = -Isrc -I/usr/local/include `pkg-config --cflags sdl2`
+libpath = -L/usr/local/lib
+
+warn = -pedantic -Wall
+
+CFLAGS = $(warn) $(opt) $(dbg) $(incpath)
+CXXFLAGS = -std=c++11 $(warn) $(opt) $(dbg) $(incpath)
+LDFLAGS = $(libpath) $(libgl_$(sys)) -lm -lgmath -lvmath -limago -lresman \
+                 -lpthread -lassimp -ltreestore -ldrawtext -loptcfg -lgoatvr `pkg-config --libs sdl2`
+
+sys = $(shell uname -s)
+libgl_Linux = -lGL -lGLU -lGLEW
+libgl_Darwin = -framework OpenGL -lGLEW
+
+$(bin): .clang_complete $(obj)
+       $(CXX) -o $@ $(obj) $(LDFLAGS)
+
+.clang_complete: Makefile
+       rm -f $@
+       for i in $(CXXFLAGS); do echo $$i >>$@; done
+
+-include $(dep)
+
+%.d: %.c
+       @$(CPP) $(CFLAGS) $< -MM -MT $(@:.d=.o) >$@
+
+%.d: %.cc
+       @$(CPP) $(CXXFLAGS) $< -MM -MT $(@:.d=.o) >$@
+
+.PHONY: clean
+clean:
+       rm -f $(obj) $(bin)
+
+.PHONY: cleandep
+cleandep:
+       rm -f $(dep)
diff --git a/sdr/post_gamma.p.glsl b/sdr/post_gamma.p.glsl
new file mode 100644 (file)
index 0000000..6b323f8
--- /dev/null
@@ -0,0 +1,10 @@
+uniform sampler2D tex;
+
+void main()
+{
+       const vec3 invgamma3 = vec3(1.0 / 2.2);
+       vec3 texel = texture2D(tex, gl_TexCoord[0].st).xyz;
+
+       gl_FragColor.rgb = pow(texel, invgamma3);
+       gl_FragColor.a = 1.0;
+}
diff --git a/sdr/post_gamma.v.glsl b/sdr/post_gamma.v.glsl
new file mode 100644 (file)
index 0000000..a8fb0cf
--- /dev/null
@@ -0,0 +1,5 @@
+void main()
+{
+       gl_Position = gl_Vertex;
+       gl_TexCoord[0] = gl_MultiTexCoord0;
+}
diff --git a/sdr/shadow-notex.p.glsl b/sdr/shadow-notex.p.glsl
new file mode 100644 (file)
index 0000000..4946ca0
--- /dev/null
@@ -0,0 +1,60 @@
+/* vi: set ft=glsl */
+#ifdef USE_CUBEMAP
+uniform samplerCube envmap;
+#endif
+uniform sampler2DShadow shadowmap;
+
+varying vec3 vdir, ldir[3], normal;
+varying vec4 shadow_tc;
+varying vec3 wdir;
+
+#define KD gl_FrontMaterial.diffuse.rgb
+#define KS gl_FrontMaterial.specular.rgb
+#define SPOW gl_FrontMaterial.shininess
+
+#define LD(i) gl_LightSource[i].diffuse.rgb
+#define LS(i) gl_LightSource[i].specular.rgb
+
+vec3 calc_diffuse(in vec3 n, in vec3 l, in vec3 lcol)
+{
+       float ndotl = max(dot(n, l), 0.0);
+       return KD * lcol * ndotl;
+}
+
+vec3 calc_specular(in vec3 n, in vec3 l, in vec3 v, in vec3 lcol)
+{
+       vec3 h = normalize(l + v);
+       float ndoth = max(dot(n, h), 0.0);
+       return KS * lcol * pow(ndoth, SPOW);
+}
+
+void main()
+{
+       float shadow = shadow2DProj(shadowmap, shadow_tc).x;
+
+       vec3 n = normalize(normal);
+       vec3 v = normalize(vdir);
+
+       vec3 l = normalize(ldir[0]);
+       vec3 diffuse = calc_diffuse(n, l, LD(0)) * shadow;
+       vec3 specular = calc_specular(n, l, v, LS(0)) * shadow;
+
+       l = normalize(ldir[1]);
+       diffuse += calc_diffuse(n, l, LD(1));
+       specular += calc_specular(n, l, v, LS(1));
+
+       l = normalize(ldir[2]);
+       diffuse += calc_diffuse(n, l, LD(2));
+       specular += calc_specular(n, l, v, LS(2));
+
+#ifdef USE_CUBEMAP
+       // envmap
+       vec3 rdir = -reflect(wdir, n);
+       vec3 env_texel = textureCube(envmap, rdir).xyz;
+       specular += KS * env_texel;
+#endif // USE_CUBEMAP
+
+       vec3 ambient = gl_LightModel.ambient.rgb * KD;
+       gl_FragColor.rgb = ambient + diffuse + specular;
+       gl_FragColor.a = gl_FrontMaterial.diffuse.a;
+}
diff --git a/sdr/shadow.v.glsl b/sdr/shadow.v.glsl
new file mode 100644 (file)
index 0000000..8f9f045
--- /dev/null
@@ -0,0 +1,27 @@
+uniform mat4 envmap_matrix;
+
+varying vec3 vdir, ldir[3], normal;
+varying vec4 shadow_tc;
+varying vec3 wdir;
+
+void main()
+{
+       gl_Position = ftransform();
+
+       vec3 vpos = (gl_ModelViewMatrix * gl_Vertex).xyz;
+       normal = gl_NormalMatrix * gl_Normal;
+       vdir = -vpos;
+       wdir = (envmap_matrix * vec4(vdir, 1.0)).xyz;   // bring back to world space
+       ldir[0] = gl_LightSource[0].position.xyz - vpos;
+       ldir[1] = gl_LightSource[1].position.xyz - vpos;
+       ldir[2] = gl_LightSource[2].position.xyz - vpos;
+       gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;
+
+       mat4 offmat = mat4(0.5, 0.0, 0.0, 0.0,
+                       0.0, 0.5, 0.0, 0.0,
+                       0.0, 0.0, 0.5, 0.0,
+                       0.5, 0.5, 0.5, 1.0);
+       mat4 tex_matrix = offmat * gl_TextureMatrix[1];
+
+       shadow_tc = tex_matrix * vec4(vpos, 1.0);
+}
diff --git a/sdr/skydome.p.glsl b/sdr/skydome.p.glsl
new file mode 100644 (file)
index 0000000..7682150
--- /dev/null
@@ -0,0 +1,12 @@
+uniform samplerCube envmap;
+
+varying vec3 normal;
+
+void main()
+{
+       vec3 n = normalize(normal);
+       vec3 texel = textureCube(envmap, n).xyz;
+
+       gl_FragColor.rgb = texel;
+       gl_FragColor.a = 1.0;
+}
diff --git a/sdr/skydome.v.glsl b/sdr/skydome.v.glsl
new file mode 100644 (file)
index 0000000..c5d3124
--- /dev/null
@@ -0,0 +1,7 @@
+varying vec3 normal;
+
+void main()
+{
+       gl_Position = ftransform();
+       normal = gl_Normal;
+}
diff --git a/src/app.cc b/src/app.cc
new file mode 100644 (file)
index 0000000..4b2ff2b
--- /dev/null
@@ -0,0 +1,589 @@
+#include <stdio.h>
+#include <assert.h>
+#include <goatvr.h>
+#include "app.h"
+#include "opengl.h"
+#include "sdr.h"
+#include "texture.h"
+#include "mesh.h"
+#include "meshgen.h"
+#include "scene.h"
+#include "metascene.h"
+#include "datamap.h"
+#include "ui.h"
+#include "opt.h"
+#include "post.h"
+#include "blob_exhibit.h"
+
+#define NEAR_CLIP      0.5
+#define FAR_CLIP       1000.0
+
+static void draw_scene();
+static void toggle_flight();
+static void calc_framerate();
+
+long time_msec;
+int win_width, win_height;
+float win_aspect;
+bool fb_srgb;
+bool opt_gear_wireframe;
+
+TextureSet texman;
+SceneSet sceneman;
+
+unsigned int sdr_shadow, sdr_shadow_notex;
+
+static float cam_dist = 0.0;
+static float cam_theta, cam_phi;
+static Vec3 cam_pos;
+static float floor_y;  // last floor height
+static float user_eye_height = 1.65;
+
+static float walk_speed = 3.0f;
+static float mouse_speed = 0.5f;
+static bool show_walk_mesh, noclip = false;
+
+static bool have_headtracking, should_swap;
+
+static int prev_mx, prev_my;
+static bool bnstate[8];
+static bool keystate[256];
+static bool gpad_bnstate[64];
+static Vec2 joy_move, joy_look;
+static float joy_deadzone = 0.01;
+
+static float framerate;
+
+static Mat4 view_matrix, mouse_view_matrix, proj_matrix;
+static MetaScene *mscn;
+static unsigned int sdr_post_gamma;
+
+static long prev_msec;
+
+static BlobExhibit *blobs;
+static bool show_blobs = 1;
+
+
+bool app_init(int argc, char **argv)
+{
+       if(!init_options(argc, argv, "demo.conf")) {
+               return false;
+       }
+       app_resize(opt.width, opt.height);
+       app_fullscreen(opt.fullscreen);
+
+       if(opt.vr) {
+               if(goatvr_init() == -1) {
+                       return false;
+               }
+               goatvr_set_origin_mode(GOATVR_HEAD);
+
+               goatvr_startvr();
+               should_swap = goatvr_should_swap() != 0;
+               user_eye_height = goatvr_get_eye_height();
+               have_headtracking = goatvr_have_headtracking();
+
+               goatvr_recenter();
+       }
+
+       int srgb_capable;
+       glGetIntegerv(GL_FRAMEBUFFER_SRGB_CAPABLE_EXT, &srgb_capable);
+       printf("Framebuffer %s sRGB-capable\n", srgb_capable ? "is" : "is not");
+       fb_srgb = srgb_capable != 0;
+       glEnable(GL_FRAMEBUFFER_SRGB);
+
+       glEnable(GL_MULTISAMPLE);
+       glEnable(GL_DEPTH_TEST);
+       glEnable(GL_CULL_FACE);
+       glEnable(GL_LIGHTING);
+       glEnable(GL_NORMALIZE);
+
+       Mesh::use_custom_sdr_attr = false;
+
+       float ambient[] = {0.0, 0.0, 0.0, 0.0};
+       glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);
+
+       glClearColor(0.1, 0.1, 0.1, 1.0);
+
+       mscn = new MetaScene;
+       if(!mscn->load(opt.scenefile ? opt.scenefile : "data/room.scene")) {
+               return false;
+       }
+
+       cam_pos = mscn->start_pos;
+       debug_log("start_pos: [%g %g %g]\n", cam_pos.x, cam_pos.y, cam_pos.z);
+       Vec3 dir = rotate(Vec3(0, 0, 1), mscn->start_rot);
+       dir.y = 0;
+       cam_theta = rad_to_deg(acos(dot(dir, Vec3(0, 0, 1))));
+
+       blobs = new BlobExhibit;
+       blobs->node = new SceneNode;
+       blobs->init();
+       blobs->node->set_position(Vec3(0, 1, 0));
+       blobs->node->update(0);
+
+       if(!(sdr_shadow_notex = create_program_load("sdr/shadow.v.glsl", "sdr/shadow-notex.p.glsl"))) {
+               return false;
+       }
+       set_uniform_int(sdr_shadow_notex, "shadowmap", 1);
+       set_uniform_int(sdr_shadow_notex, "envmap", 2);
+
+       if(!fb_srgb) {
+               sdr_post_gamma = create_program_load("sdr/post_gamma.v.glsl", "sdr/post_gamma.p.glsl");
+       }
+
+       glUseProgram(0);
+
+       if(opt.vr || opt.fullscreen) {
+               app_grab_mouse(true);
+       }
+       return true;
+}
+
+void app_cleanup()
+{
+       app_grab_mouse(false);
+       if(opt.vr) {
+               goatvr_shutdown();
+       }
+
+       blobs->destroy();
+       delete blobs->node;
+       delete blobs;
+
+       texman.clear();
+       sceneman.clear();
+}
+
+static bool constrain_walk_mesh(const Vec3 &v, Vec3 *newv)
+{
+       Mesh *wm = mscn->walk_mesh;
+       if(!wm) {
+               *newv = v;
+               return true;
+       }
+
+       Ray downray = Ray(v, Vec3(0, -1, 0));
+       HitPoint hit;
+       if(mscn->walk_mesh->intersect(downray, &hit)) {
+               *newv = hit.pos;
+               newv->y += user_eye_height;
+               return true;
+       }
+       return false;
+}
+
+static void update(float dt)
+{
+       texman.update();
+       sceneman.update();
+
+       mscn->update(dt);
+       if(show_blobs) {
+               blobs->update(dt);
+       }
+
+       float speed = walk_speed * dt;
+       Vec3 dir;
+
+       // joystick
+       float jdeadsq = joy_deadzone * joy_deadzone;
+       float jmove_lensq = length_sq(joy_move);
+       float jlook_lensq = length_sq(joy_look);
+
+       if(jmove_lensq > jdeadsq) {
+               float len = sqrt(jmove_lensq);
+               jmove_lensq -= jdeadsq;
+
+               float mag = len * len;
+               dir.x += mag * joy_move.x / len * 2.0 * speed;
+               dir.z += mag * joy_move.y / len * 2.0 * speed;
+       }
+       if(jlook_lensq > jdeadsq) {
+               float len = sqrt(jlook_lensq);
+               jlook_lensq -= jdeadsq;
+
+               float mag = len * len;
+               cam_theta += mag * joy_look.x / len * 200.0 * dt;
+               cam_phi += mag * joy_look.y / len * 100.0 * dt;
+               if(cam_phi < -90.0f) cam_phi = -90.0f;
+               if(cam_phi > 90.0f) cam_phi = 90.0f;
+       }
+
+       // keyboard move
+       if(keystate[(int)'w']) {
+               dir.z -= speed;
+       }
+       if(keystate[(int)'s']) {
+               dir.z += speed;
+       }
+       if(keystate[(int)'d']) {
+               dir.x += speed;
+       }
+       if(keystate[(int)'a']) {
+               dir.x -= speed;
+       }
+       if(keystate[(int)'q'] || gpad_bnstate[GPAD_UP]) {
+               cam_pos.y += speed;
+       }
+       if(keystate[(int)'z'] || gpad_bnstate[GPAD_DOWN]) {
+               cam_pos.y -= speed;
+       }
+
+       float theta = M_PI * cam_theta / 180.0f;
+       Vec3 newpos;
+       newpos.x = cam_pos.x + cos(theta) * dir.x - sin(theta) * dir.z;
+       newpos.y = cam_pos.y;
+       newpos.z = cam_pos.z + sin(theta) * dir.x + cos(theta) * dir.z;
+
+       if(noclip) {
+               cam_pos = newpos;
+       } else {
+               if(!constrain_walk_mesh(newpos, &cam_pos)) {
+                       float dtheta = M_PI / 32.0;
+                       float theta = dtheta;
+                       Vec2 dir2d = newpos.xz() - cam_pos.xz();
+
+                       for(int i=0; i<16; i++) {
+                               Vec2 dvec = rotate(dir2d, theta);
+                               Vec3 pos = cam_pos + Vec3(dvec.x, 0, dvec.y);
+                               if(constrain_walk_mesh(pos, &cam_pos)) {
+                                       break;
+                               }
+                               dvec = rotate(dir2d, -theta);
+                               pos = cam_pos + Vec3(dvec.x, 0, dvec.y);
+                               if(constrain_walk_mesh(pos, &cam_pos)) {
+                                       break;
+                               }
+                               theta += dtheta;
+                       }
+               }
+               floor_y = cam_pos.y - user_eye_height;
+       }
+
+       // calculate mouselook view matrix
+       mouse_view_matrix = Mat4::identity;
+       mouse_view_matrix.pre_translate(0, 0, -cam_dist);
+       if(!have_headtracking) {
+               mouse_view_matrix.pre_rotate_x(deg_to_rad(cam_phi));
+       }
+       mouse_view_matrix.pre_rotate_y(deg_to_rad(cam_theta));
+       mouse_view_matrix.pre_translate(-cam_pos.x, -cam_pos.y, -cam_pos.z);
+}
+
+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);
+}
+
+void app_display()
+{
+       float dt = (float)(time_msec - prev_msec) / 1000.0f;
+       prev_msec = time_msec;
+
+       update(dt);
+
+       if(opt.vr) {
+               // VR mode
+               goatvr_draw_start();
+               glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+               for(int i=0; i<2; i++) {
+                       // for each eye
+                       goatvr_draw_eye(i);
+
+                       proj_matrix = goatvr_projection_matrix(i, NEAR_CLIP, FAR_CLIP);
+                       glMatrixMode(GL_PROJECTION);
+                       glLoadMatrixf(proj_matrix[0]);
+
+                       view_matrix = mouse_view_matrix * Mat4(goatvr_view_matrix(i));
+                       glMatrixMode(GL_MODELVIEW);
+                       glLoadMatrixf(view_matrix[0]);
+
+                       draw_scene();
+               }
+               goatvr_draw_done();
+
+               if(should_swap) {
+                       app_swap_buffers();
+               }
+
+       } else {
+               glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+               proj_matrix.perspective(deg_to_rad(50.0), win_aspect, NEAR_CLIP, FAR_CLIP);
+               glMatrixMode(GL_PROJECTION);
+               glLoadMatrixf(proj_matrix[0]);
+
+               view_matrix = mouse_view_matrix;
+               glMatrixMode(GL_MODELVIEW);
+               glLoadMatrixf(view_matrix[0]);
+
+               draw_scene();
+
+               if(!fb_srgb && sdr_post_gamma) {
+                       slow_post(sdr_post_gamma);
+                       glUseProgram(0);
+               }
+               app_swap_buffers();
+       }
+       assert(glGetError() == GL_NO_ERROR);
+
+       calc_framerate();
+}
+
+
+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);
+
+       mscn->draw();
+       if(show_blobs) {
+               blobs->draw();
+       }
+
+       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);
+
+               glPolygonOffset(-1, 1);
+               glDepthMask(0);
+
+               glColor3f(0.3, 0.08, 0.01);
+               mscn->walk_mesh->draw();
+
+               glDepthMask(1);
+
+               glPopAttrib();
+       }
+
+       print_text(Vec2(9 * win_width / 10, 20), Vec3(1, 1, 0), "fps: %.1f", framerate);
+       draw_ui();
+}
+
+
+void app_reshape(int x, int y)
+{
+       glViewport(0, 0, x, y);
+       goatvr_set_fb_size(x, y, 1.0f);
+}
+
+void app_keyboard(int key, bool pressed)
+{
+       unsigned int mod = app_get_modifiers();
+
+       if(pressed) {
+               switch(key) {
+               case 27:
+                       app_quit();
+                       break;
+
+               case '\n':
+               case '\r':
+                       if(mod & MOD_ALT) {
+                               app_toggle_fullscreen();
+                       }
+                       break;
+
+               case '`':
+                       app_toggle_grab_mouse();
+                       show_message("mouse %s", app_is_mouse_grabbed() ? "grabbed" : "released");
+                       break;
+
+               case 'w':
+                       if(mod & MOD_CTRL) {
+                               show_walk_mesh = !show_walk_mesh;
+                               show_message("walk mesh: %s", show_walk_mesh ? "on" : "off");
+                       }
+                       break;
+
+               case 'c':
+                       if(mod & MOD_CTRL) {
+                               noclip = !noclip;
+                               show_message(noclip ? "no clip" : "clip");
+                       }
+                       break;
+
+               case 'f':
+                       toggle_flight();
+                       break;
+
+               case 'p':
+                       if(mod & MOD_CTRL) {
+                               fb_srgb = !fb_srgb;
+                               show_message("gamma correction for non-sRGB framebuffers: %s\n", fb_srgb ? "off" : "on");
+                       }
+                       break;
+
+               case '=':
+                       walk_speed *= 1.25;
+                       show_message("walk speed: %g", walk_speed);
+                       break;
+
+               case '-':
+                       walk_speed *= 0.75;
+                       show_message("walk speed: %g", walk_speed);
+                       break;
+
+               case ']':
+                       mouse_speed *= 1.2;
+                       show_message("mouse speed: %g", mouse_speed);
+                       break;
+
+               case '[':
+                       mouse_speed *= 0.8;
+                       show_message("mouse speed: %g", mouse_speed);
+                       break;
+
+               case 'b':
+                       show_blobs = !show_blobs;
+                       show_message("blobs: %s\n", show_blobs ? "on" : "off");
+                       break;
+               }
+       }
+
+       if(key < 256 && !(mod & (MOD_CTRL | MOD_ALT))) {
+               keystate[key] = pressed;
+       }
+}
+
+void app_mouse_button(int bn, bool pressed, int x, int y)
+{
+       prev_mx = x;
+       prev_my = y;
+       bnstate[bn] = pressed;
+}
+
+static inline void mouse_look(float dx, float dy)
+{
+       float scrsz = (float)win_height;
+       cam_theta += dx * 512.0 / scrsz;
+       cam_phi += dy * 512.0 / scrsz;
+
+       if(cam_phi < -90) cam_phi = -90;
+       if(cam_phi > 90) cam_phi = 90;
+}
+
+static void mouse_zoom(float dx, float dy)
+{
+       cam_dist += dy * 0.1;
+       if(cam_dist < 0.0) cam_dist = 0.0;
+}
+
+void app_mouse_motion(int x, int y)
+{
+       int dx = x - prev_mx;
+       int dy = y - prev_my;
+       prev_mx = x;
+       prev_my = y;
+
+       if(!dx && !dy) return;
+
+       if(bnstate[0]) {
+               mouse_look(dx, dy);
+       }
+       if(bnstate[2]) {
+               mouse_zoom(dx, dy);
+       }
+}
+
+void app_mouse_delta(int dx, int dy)
+{
+       if(bnstate[2]) {
+               mouse_zoom(dx * mouse_speed, dy * mouse_speed);
+       } else {
+               mouse_look(dx * mouse_speed, dy * mouse_speed);
+       }
+}
+
+void app_gamepad_axis(int axis, float val)
+{
+       switch(axis) {
+       case 0:
+               joy_move.x = val;
+               break;
+       case 1:
+               joy_move.y = val;
+               break;
+
+       case 2:
+               joy_look.x = val;
+               break;
+       case 3:
+               joy_look.y = val;
+               break;
+       }
+}
+
+void app_gamepad_button(int bn, bool pressed)
+{
+       gpad_bnstate[bn] = pressed;
+
+       if(pressed) {
+               switch(bn) {
+               case GPAD_LSTICK:
+                       toggle_flight();
+                       break;
+
+               case GPAD_X:
+                       show_blobs = !show_blobs;
+                       show_message("blobs: %s\n", show_blobs ? "on" : "off");
+                       break;
+
+               default:
+                       break;
+               }
+       }
+}
+
+static void toggle_flight()
+{
+       static float prev_walk_speed = -1.0;
+       if(prev_walk_speed < 0.0) {
+               noclip = true;
+               prev_walk_speed = walk_speed;
+               walk_speed = 10.0;
+               show_message("fly mode\n");
+       } else {
+               noclip = false;
+               walk_speed = prev_walk_speed;
+               prev_walk_speed = -1.0;
+               show_message("walk mode\n");
+       }
+}
+
+static void calc_framerate()
+{
+       //static int ncalc;
+       static int nframes;
+       static long prev_upd;
+
+       long elapsed = time_msec - prev_upd;
+       if(elapsed >= 1000) {
+               framerate = (float)nframes / (float)(elapsed * 0.001);
+               nframes = 1;
+               prev_upd = time_msec;
+
+               /*if(++ncalc >= 5) {
+                       printf("fps: %f\n", framerate);
+                       ncalc = 0;
+               }*/
+       } else {
+               ++nframes;
+       }
+}
diff --git a/src/app.h b/src/app.h
new file mode 100644 (file)
index 0000000..603cc34
--- /dev/null
+++ b/src/app.h
@@ -0,0 +1,70 @@
+#ifndef APP_H_
+#define APP_H_
+
+#include "texture.h"
+#include "scene.h"
+
+extern long time_msec;
+extern int win_width, win_height;
+extern float win_aspect;
+extern bool opt_gear_wireframe;
+extern bool fb_srgb;
+
+extern TextureSet texman;
+extern SceneSet sceneman;
+
+extern unsigned int sdr_shadow, sdr_shadow_notex;
+
+enum {
+       MOD_SHIFT       = 1,
+       MOD_ALT         = 2,
+       MOD_CTRL        = 4
+};
+
+/* XXX make sure these match with SDL_GameControllerButton */
+enum {
+       GPAD_A,
+       GPAD_B,
+       GPAD_X,
+       GPAD_Y,
+       GPAD_BACK,
+       GPAD_GUIDE,
+       GPAD_START,
+       GPAD_LSTICK,
+       GPAD_RSTICK,
+       GPAD_L,
+       GPAD_R,
+       GPAD_UP,
+       GPAD_DOWN,
+       GPAD_LEFT,
+       GPAD_RIGHT,
+};
+
+bool app_init(int argc, char **argv);
+void app_cleanup();
+
+void app_display();
+void app_reshape(int x, int y);
+
+void app_keyboard(int key, bool pressed);
+void app_mouse_button(int bn, bool pressed, int x, int y);
+void app_mouse_motion(int x, int y);
+void app_mouse_delta(int dx, int dy);
+
+void app_gamepad_axis(int axis, float val);
+void app_gamepad_button(int bn, bool pressed);
+
+// the following functions are implemented by the backend (main.cc)
+void app_quit();
+void app_swap_buffers();
+unsigned int app_get_modifiers();
+
+void app_resize(int x, int y);
+void app_fullscreen(bool fs);
+void app_toggle_fullscreen();
+bool app_is_fullscreen();
+void app_grab_mouse(bool grab);
+void app_toggle_grab_mouse();
+bool app_is_mouse_grabbed();
+
+#endif // APP_H_
diff --git a/src/blob_exhibit.cc b/src/blob_exhibit.cc
new file mode 100644 (file)
index 0000000..0378359
--- /dev/null
@@ -0,0 +1,144 @@
+#include "blob_exhibit.h"
+#include "blobs/metasurf.h"
+#include "app.h"
+
+struct Metaball {
+       Vec3 pos;
+       float energy;
+       Vec3 path_scale, path_offset;
+       float phase_offset, speed;
+};
+#define NUM_MBALLS     8
+
+static Metaball def_mball_data[] = {
+       {Vec3(0, 0, 0), 2.18038,  Vec3(1.09157, 1.69766, 1),   Vec3(0.622818, 0.905624, 0), 1.24125, 0.835223},
+       {Vec3(0, 0, 0), 2.03646,  Vec3(0.916662, 1.2161, 1),   Vec3(0.118734, 0.283516, 0), 2.29201, 1.0134},
+       {Vec3(0, 0, 0), 2.40446,  Vec3(1.87429, 1.57595, 1),   Vec3(0.298566, -0.788474, 0), 3.8137, 0.516301},
+       {Vec3(0, 0, 0), 0.985774, Vec3(0.705847, 0.735019, 1), Vec3(0.669189, -0.217922, 0), 0.815497, 0.608809},
+       {Vec3(0, 0, 0), 2.49785,  Vec3(0.827385, 1.75867, 1),  Vec3(0.0284513, 0.247808, 0), 1.86002, 1.13755},
+       {Vec3(0, 0, 0), 1.54857,  Vec3(1.24037, 0.938775, 1),  Vec3(1.04011, 0.596987, 0), 3.30964, 1.26991},
+       {Vec3(0, 0, 0), 1.30046,  Vec3(1.83729, 1.02869, 1),   Vec3(-0.476708, 0.676994, 0), 5.77441, 0.569755},
+       {Vec3(0, 0, 0), 2.39865,  Vec3(1.28899, 0.788321, 1),  Vec3(-0.910677, 0.359099, 0), 5.5935, 0.848893}
+};
+
+struct BlobPriv {
+       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];
+       }
+}
+
+BlobExhibit::~BlobExhibit()
+{
+       delete priv;
+}
+
+bool BlobExhibit::init()
+{
+       if(!(priv->msurf = msurf_create())) {
+               return false;
+       }
+       msurf_set_user_data(priv->msurf, priv);
+       msurf_set_threshold(priv->msurf, 8);
+       msurf_set_inside(priv->msurf, MSURF_GREATER);
+       msurf_set_bounds(priv->msurf, -3.5, 3.5, -3.5, 3.5, -3.5, 3.5);
+       msurf_eval_func(priv->msurf, eval);
+       msurf_vertex_func(priv->msurf, vertex);
+
+       priv->tex = texman.get_texture("data/sphmap.jpg");
+       return true;
+}
+
+void BlobExhibit::destroy()
+{
+       msurf_free(priv->msurf);
+       priv->msurf = 0;
+}
+
+void BlobExhibit::update(float dt)
+{
+       double sec = time_msec / 1000.0;
+
+       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;
+       }
+}
+
+void BlobExhibit::draw() const
+{
+       pre_draw();
+
+       glPushAttrib(GL_ENABLE_BIT);
+
+       glUseProgram(0);
+
+       glDisable(GL_LIGHTING);
+       glEnable(GL_TEXTURE_2D);
+       priv->tex->bind();
+
+       glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
+       glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
+       glEnable(GL_TEXTURE_GEN_S);
+       glEnable(GL_TEXTURE_GEN_T);
+
+       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);
+
+       glLoadIdentity();
+       glMatrixMode(GL_MODELVIEW);
+
+       glPopAttrib();
+
+       post_draw();
+}
+
+static void vertex(struct metasurface *ms, float x, float y, float z)
+{
+       static const float delta = 0.01;
+
+       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;
+
+       glNormal3f(dfdx, dfdy, dfdz);
+       glVertex3f(x, y, z);
+}
+
+static float eval(struct metasurface *ms, float x, float y, float z)
+{
+       float sum = 0.0f;
+       BlobPriv *priv = (BlobPriv*)msurf_get_user_data(ms);
+
+       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;
+
+               sum += priv->mballs[i].energy / dsq;
+       }
+       return sum;
+}
diff --git a/src/blob_exhibit.h b/src/blob_exhibit.h
new file mode 100644 (file)
index 0000000..001047a
--- /dev/null
@@ -0,0 +1,23 @@
+#ifndef BLOB_EXHIBIT_H_
+#define BLOB_EXHIBIT_H_
+
+#include "exhibit.h"
+
+struct BlobPriv;
+
+class BlobExhibit : public Exhibit {
+private:
+       BlobPriv *priv;
+
+public:
+       BlobExhibit();
+       ~BlobExhibit();
+
+       bool init();
+       void destroy();
+
+       void update(float dt);
+       void draw() const;
+};
+
+#endif // BLOB_EXHIBIT_H_
diff --git a/src/blobs/mcubes.h b/src/blobs/mcubes.h
new file mode 100644 (file)
index 0000000..2a9034d
--- /dev/null
@@ -0,0 +1,294 @@
+static int mc_edge_table[256] = {
+       0x0  , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c,
+       0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
+       0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c,
+       0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
+       0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c,
+       0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
+       0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac,
+       0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
+       0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c,
+       0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
+       0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc,
+       0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
+       0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c,
+       0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
+       0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc ,
+       0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
+       0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc,
+       0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
+       0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c,
+       0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
+       0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc,
+       0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
+       0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c,
+       0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460,
+       0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac,
+       0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0,
+       0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c,
+       0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230,
+       0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c,
+       0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190,
+       0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c,
+       0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0
+};
+
+
+static int mc_tri_table[256][16] = {
+       {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1},
+       {3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1},
+       {3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1},
+       {3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1},
+       {9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1},
+       {1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1},
+       {9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1},
+       {2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1},
+       {8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1},
+       {9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1},
+       {4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1},
+       {3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1},
+       {1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1},
+       {4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1},
+       {4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1},
+       {9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1},
+       {1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1},
+       {5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1},
+       {2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1},
+       {9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1},
+       {0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1},
+       {2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1},
+       {10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1},
+       {4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1},
+       {5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1},
+       {5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1},
+       {9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1},
+       {0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1},
+       {1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1},
+       {10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1},
+       {8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1},
+       {2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1},
+       {7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1},
+       {9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1},
+       {2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1},
+       {11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1},
+       {9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1},
+       {5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1},
+       {11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1},
+       {11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1},
+       {1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1},
+       {9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1},
+       {5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1},
+       {2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1},
+       {0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1},
+       {5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1},
+       {6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1},
+       {0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1},
+       {3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1},
+       {6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1},
+       {5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1},
+       {1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1},
+       {10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1},
+       {6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1},
+       {1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1},
+       {8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1},
+       {7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1},
+       {3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1},
+       {5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1},
+       {0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1},
+       {9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1},
+       {8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1},
+       {5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1},
+       {0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1},
+       {6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1},
+       {10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1},
+       {10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1},
+       {8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1},
+       {1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1},
+       {3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1},
+       {0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1},
+       {10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1},
+       {0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1},
+       {3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1},
+       {6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1},
+       {9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1},
+       {8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1},
+       {3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1},
+       {6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1},
+       {0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1},
+       {10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1},
+       {10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1},
+       {1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1},
+       {2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1},
+       {7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1},
+       {7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1},
+       {2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1},
+       {1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1},
+       {11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1},
+       {8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1},
+       {0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1},
+       {7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1},
+       {10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1},
+       {2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1},
+       {6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1},
+       {7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1},
+       {2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1},
+       {1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1},
+       {10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1},
+       {10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1},
+       {0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1},
+       {7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1},
+       {6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1},
+       {8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1},
+       {9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1},
+       {6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1},
+       {1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1},
+       {4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1},
+       {10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1},
+       {8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1},
+       {0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1},
+       {1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1},
+       {8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1},
+       {10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1},
+       {4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1},
+       {10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1},
+       {5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1},
+       {11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1},
+       {9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1},
+       {6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1},
+       {7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1},
+       {3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1},
+       {7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1},
+       {9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1},
+       {3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1},
+       {6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1},
+       {9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1},
+       {1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1},
+       {4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1},
+       {7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1},
+       {6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1},
+       {3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1},
+       {0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1},
+       {6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1},
+       {1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1},
+       {0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1},
+       {11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1},
+       {6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1},
+       {5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1},
+       {9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1},
+       {1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1},
+       {1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1},
+       {10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1},
+       {0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1},
+       {5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1},
+       {10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1},
+       {11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1},
+       {0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1},
+       {9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1},
+       {7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1},
+       {2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1},
+       {8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1},
+       {9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1},
+       {9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1},
+       {1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1},
+       {9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1},
+       {9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1},
+       {5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1},
+       {0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1},
+       {10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1},
+       {2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1},
+       {0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1},
+       {0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1},
+       {9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1},
+       {5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1},
+       {3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1},
+       {5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1},
+       {8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1},
+       {0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1},
+       {9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1},
+       {0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1},
+       {1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1},
+       {3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1},
+       {4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1},
+       {9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1},
+       {11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1},
+       {11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1},
+       {2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1},
+       {9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1},
+       {3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1},
+       {1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1},
+       {4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1},
+       {4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1},
+       {0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1},
+       {3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1},
+       {3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1},
+       {0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1},
+       {9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1},
+       {1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+       {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}
+};
diff --git a/src/blobs/metasurf.c b/src/blobs/metasurf.c
new file mode 100644 (file)
index 0000000..2207b3d
--- /dev/null
@@ -0,0 +1,452 @@
+/*
+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 "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
+
+typedef float vec3[3];
+
+struct metasurface {
+       vec3 min, max;
+       int res[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;
+
+       vec3 vbuf[3];
+       int nverts;
+};
+
+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
+
+
+struct metasurface *msurf_create(void)
+{
+       struct metasurface *ms;
+
+       if(!(ms = malloc(sizeof *ms))) {
+               return 0;
+       }
+       if(msurf_init(ms) == -1) {
+               free(ms);
+       }
+       return ms;
+}
+
+void msurf_free(struct metasurface *ms)
+{
+       free(ms);
+}
+
+static int msurf_init(struct metasurface *ms)
+{
+       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->dx = ms->dy = ms->dz = 0.001;
+       ms->flip = 0;
+
+       return 0;
+}
+
+void msurf_set_user_data(struct metasurface *ms, void *udata)
+{
+       ms->udata = udata;
+}
+
+void *msurf_get_user_data(struct metasurface *ms)
+{
+       return ms->udata;
+}
+
+void msurf_set_inside(struct metasurface *ms, int inside)
+{
+       switch(inside) {
+       case MSURF_GREATER:
+               ms->flip = 0;
+               break;
+
+       case MSURF_LESS:
+               ms->flip = 1;
+               break;
+
+       default:
+               fprintf(stderr, "msurf_inside expects MSURF_GREATER or MSURF_LESS\n");
+       }
+}
+
+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;
+}
+
+void msurf_set_bounds(struct metasurface *ms, float xmin, float ymin, float zmin, float xmax, float ymax, float zmax)
+{
+       ms->min[0] = xmin;
+       ms->min[1] = ymin;
+       ms->min[2] = zmin;
+       ms->max[0] = xmax;
+       ms->max[1] = ymax;
+       ms->max[2] = zmax;
+}
+
+void msurf_get_bounds(struct metasurface *ms, float *xmin, float *ymin, float *zmin, float *xmax, float *ymax, float *zmax)
+{
+       *xmin = ms->min[0];
+       *ymin = ms->min[1];
+       *zmin = ms->min[2];
+       *xmax = ms->max[0];
+       *ymax = ms->max[1];
+       *zmax = ms->max[2];
+}
+
+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;
+}
+
+void msurf_get_resolution(struct metasurface *ms, int *xres, int *yres, int *zres)
+{
+       *xres = ms->res[0];
+       *yres = ms->res[1];
+       *zres = ms->res[2];
+}
+
+void msurf_set_threshold(struct metasurface *ms, float thres)
+{
+       ms->thres = thres;
+}
+
+float msurf_get_threshold(struct metasurface *ms)
+{
+       return ms->thres;
+}
+
+
+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;
+       }
+
+       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[2] += delta[2];
+                       }
+                       pos[1] += delta[1];
+               }
+               pos[0] += delta[0];
+       }
+       return 0;
+}
+
+
+static void process_cell(struct metasurface *ms, vec3 pos, vec3 sz)
+{
+       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
+}
+
+
+/* ---- marching cubes implementation ---- */
+#ifdef USE_MCUBES
+
+static unsigned int mc_bitcode(float *val, float thres);
+
+static void process_cube(struct metasurface *ms, vec3 *pos, float *val)
+{
+       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;
+       }
+
+       if(mc_edge_table[code] == 0) {
+               return;
+       }
+
+       for(i=0; i<12; i++) {
+               if(mc_edge_table[code] & (1 << i)) {
+                       int p0 = pidx[i][0];
+                       int p1 = pidx[i][1];
+
+                       float t = (ms->thres - val[p0]) / (val[p1] - val[p0]);
+                       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;
+               }
+       }
+
+       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;
+                               }
+                               ms->normal(ms, dfdx, dfdy, dfdz);
+                       }
+
+                       /* TODO multithreadied polygon emmit */
+                       ms->vertex(ms, v[0], v[1], v[2]);
+               }
+       }
+}
+
+static unsigned int mc_bitcode(float *val, float thres)
+{
+       unsigned int i, res = 0;
+
+       for(i=0; i<8; i++) {
+               if(val[i] > thres) {
+                       res |= 1 << i;
+               }
+       }
+       return res;
+}
+#endif /* USE_MCUBES */
+
+
+/* ---- marching tetrahedra implementation (incomplete) ---- */
+#ifdef USE_MTETRA
+
+static unsigned int mt_bitcode(float v0, float v1, float v2, float v3, float thres);
+static void emmit(struct metasurface *ms, float v0, float v1, vec3 p0, vec3 p1, int rev)
+
+
+#define REVBIT(x)      ((x) & 8)
+#define INV(x)         (~(x) & 0xf)
+#define EDGE(a, b)     emmit(ms, val[idx[a]], val[idx[b]], pos[idx[a]], pos[idx[b]], REVBIT(code))
+static void process_tetra(struct metasurface *ms, int *idx, vec3 *pos, float *val)
+{
+       unsigned int code = mt_bitcode(val[idx[0]], val[idx[1]], val[idx[2]], val[idx[3]], ms->thres);
+
+       switch(code) {
+       case 1:
+       case INV(1):
+               EDGE(0, 1);
+               EDGE(0, 2);
+               EDGE(0, 3);
+               break;
+
+       case 2:
+       case INV(2):
+               EDGE(1, 0);
+               EDGE(1, 3);
+               EDGE(1, 2);
+               break;
+
+       case 3:
+       case INV(3):
+               EDGE(0, 3);
+               EDGE(0, 2);
+               EDGE(1, 3);
+
+               EDGE(1, 3);
+               EDGE(1, 2);
+               EDGE(0, 2);
+               break;
+
+       case 4:
+       case INV(4):
+               EDGE(2, 0);
+               EDGE(2, 1);
+               EDGE(2, 3);
+               break;
+
+       case 5:
+       case INV(5):
+               EDGE(0, 1);
+               EDGE(2, 3);
+               EDGE(0, 3);
+
+               EDGE(0, 1);
+               EDGE(1, 2);
+               EDGE(2, 3);
+               break;
+
+       case 6:
+       case INV(6):
+               EDGE(0, 1);
+               EDGE(1, 3);
+               EDGE(2, 3);
+
+               EDGE(0, 1);
+               EDGE(0, 2);
+               EDGE(2, 3);
+               break;
+
+       case 7:
+       case INV(7):
+               EDGE(3, 0);
+               EDGE(3, 2);
+               EDGE(3, 1);
+               break;
+
+       default:
+               break;  /* cases 0 and 15 */
+       }
+}
+
+#define BIT(i) ((v##i > thres) ? (1 << i) : 0)
+static unsigned int mt_bitcode(float v0, float v1, float v2, float v3, float thres)
+{
+       return BIT(0) | BIT(1) | BIT(2) | BIT(3);
+}
+
+static void emmit(struct metasurface *ms, float v0, float v1, vec3 p0, vec3 p1, int rev)
+{
+       int i;
+       float t = (ms->thres - v0) / (v1 - v0);
+
+       vec3 p;
+       for(i=0; i<3; i++) {
+               p[i] = p0[i] + (p1[i] - p0[i]) * t;
+       }
+       ms->vertex(ms, p[0], p[1], p[2]);
+
+       /*for(i=0; i<3; i++) {
+               ms->vbuf[ms->nverts][i] = p0[i] + (p1[i] - p0[i]) * t;
+       }
+
+       if(++ms->nverts >= 3) {
+               ms->nverts = 0;
+
+               for(i=0; i<3; i++) {
+                       int idx = rev ? (2 - i) : i;
+                       ms->vertex(ms, ms->vbuf[idx][0], ms->vbuf[idx][1], ms->vbuf[idx][2]);
+               }
+       }*/
+}
+#endif /* USE_MTETRA */
diff --git a/src/blobs/metasurf.h b/src/blobs/metasurf.h
new file mode 100644 (file)
index 0000000..77d2920
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+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;
+
+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);
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+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);
+
+/* 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_bounds(struct metasurface *ms, float xmin, float ymin, float zmin, float xmax, float ymax, float zmax);
+void msurf_get_bounds(struct metasurface *ms, float *xmin, float *ymin, float *zmin, float *xmax, float *ymax, float *zmax);
+
+/* resolution of the 3D evaluation grid, the bigger, the better, the slower
+ * (default: 40, 40, 40)
+ */
+void msurf_set_resolution(struct metasurface *ms, int xres, int yres, int zres);
+void msurf_get_resolution(struct metasurface *ms, int *xres, int *yres, int *zres);
+
+/* isosurface threshold value (default: 0) */
+void msurf_set_threshold(struct metasurface *ms, float thres);
+float msurf_get_threshold(struct metasurface *ms);
+
+
+/* finally call this to perform the polygonization */
+int msurf_polygonize(struct metasurface *ms);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* METASURF_H_ */
diff --git a/src/datamap.cc b/src/datamap.cc
new file mode 100644 (file)
index 0000000..e466082
--- /dev/null
@@ -0,0 +1,153 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "datamap.h"
+
+#ifdef WIN32
+#include <malloc.h>
+#else
+#include <alloca.h>
+#endif
+
+static char *clean_line(char *s);
+
+void DataMap::clear()
+{
+       dmap.clear();
+       cache.clear();
+}
+
+void DataMap::set_path(const char *path)
+{
+       root = std::string(path);
+}
+
+bool DataMap::load_map(const char *fname)
+{
+       std::string path = root.empty() ? fname : root + std::string("/") + fname;
+       fname = path.c_str();
+
+       FILE *fp = fopen(fname, "r");
+       if(!fp) {
+               fprintf(stderr, "failed to open data map: %s\n", fname);
+               return false;
+       }
+
+       char buf[256];
+       if(fread(buf, 1, 8, fp) < 8 || memcmp(buf, "DATAMAP0", 8) != 0) {
+               fprintf(stderr, "invalid datamap file: %s\n", fname);
+               fclose(fp);
+               return false;
+       }
+
+       clear();
+
+       char *line;
+       int nline = 0;
+       while(fgets(buf, sizeof buf, fp)) {
+               ++nline;
+               line = clean_line(buf);
+               if(!line || !*line) continue;
+
+               char *colon = strchr(line, ':');
+               if(!colon) {
+                       goto err;
+               }
+               *colon = 0;
+
+               //std::pair<std::regex, std::string> pair;
+               //pair.first = std::regex(line);
+               std::pair<std::string, std::string> pair;
+               pair.first = std::string(line);
+
+               char *value = clean_line(colon + 1);
+               if(!value || !*value) {
+                       goto err;
+               }
+               pair.second = std::string(value);
+               dmap.push_back(pair);
+       }
+       fclose(fp);
+
+       printf("loaded datamap %s: %d mappings\n", fname, (int)dmap.size());
+       return true;
+
+err:
+       fprintf(stderr, "error while parsing %s, invalid line %d: %s\n", fname, nline, line);
+       clear();
+       fclose(fp);
+       return false;
+}
+
+void DataMap::map(const char *match, const char *path)
+{
+       std::pair<std::string, std::string> mapping;
+       mapping.first = std::string(match);
+       mapping.second = std::string(path);
+       dmap.push_back(std::move(mapping));
+}
+
+int DataMap::lookup(const char *in, char *buf, int bsz) const
+{
+       std::string res;
+
+       char *inbuf = (char*)alloca(strlen(in) + 1);
+       strcpy(inbuf, in);
+       in = clean_line(inbuf);
+
+       // first check the cache
+       std::map<std::string, std::string>::iterator it = cache.find(in);
+       if(it != cache.end()) {
+               res = it->second;
+       } else {
+               // try matching with the available mappings
+               res = root.empty() ? std::string(in) : root + "/" + std::string(in);
+
+               int num = dmap.size();
+               for(int i=0; i<num; i++) {
+                       //if(std::regex_search(in, dmap[i].first)) {
+                       if(strstr(in, dmap[i].first.c_str())) {
+                               res = root.empty() ? dmap[i].second : root + "/" + dmap[i].second;
+                               cache[in] = res;        // add it to the cache
+                               break;
+                       }
+               }
+       }
+
+       // copy result in buf, truncating if necessary and return the size of the
+       // buffer required to hold it
+       if(buf) {
+               int n = std::min(bsz - 1, (int)res.length());
+               memcpy(buf, res.c_str(), n);
+               buf[n] = 0; // make sure it's null-terminated even if it got truncated
+       }
+       return res.length() + 1;
+}
+
+int DataMap::path_size(const char *in) const
+{
+       return lookup(in, 0, 0);
+}
+
+static char *clean_line(char *s)
+{
+       while(*s && isspace(*s)) ++s;
+       if(!*s) return 0;
+
+       char *end;
+       if(!(end = strchr(s, '#'))) {
+               end = s + strlen(s) - 1;
+       }
+       while(end > s && isspace(*end)) --end;
+       if(s == end) return 0;
+       end[1] = 0;
+
+       // app-specific: convert backslashes
+       char *c = s;
+       while(*c) {
+               if(*c == '\\') *c = '/';
+               ++c;
+       }
+
+       return s;
+}
diff --git a/src/datamap.h b/src/datamap.h
new file mode 100644 (file)
index 0000000..137fdca
--- /dev/null
@@ -0,0 +1,25 @@
+#ifndef DATAMAP_H_
+#define DATAMAP_H_
+
+#include <vector>
+#include <map>
+#include <string>
+
+class DataMap {
+       std::vector<std::pair<std::string, std::string>> dmap;
+       mutable std::map<std::string, std::string> cache;
+       std::string root;
+
+public:
+       void clear();
+
+       void set_path(const char *path);
+
+       bool load_map(const char *fname);
+       void map(const char *match, const char *path);
+
+       int lookup(const char *in, char *buf, int bsz) const;
+       int path_size(const char *in) const;
+};
+
+#endif // DATAMAP_H_
diff --git a/src/dataset.h b/src/dataset.h
new file mode 100644 (file)
index 0000000..a4736c4
--- /dev/null
@@ -0,0 +1,53 @@
+/** DataSet is a generic resource database with fast O(logn) lookups by name
+ * it can be used for texture managers, mesh managers, sound effect managers etc
+ *
+ * The constructor takes a load function and a destructor function to be called
+ * when a nonexistent resource is requested and needs to be loaded, and when
+ * the DataSet is destroyed. The destructor is optional and can be set to null
+ * if not needed.
+ *
+ * Requesting a resource works by simply calling get, example:
+ * ----------------------------------------------------------
+ * \code
+ * Texture *load_texture(const char *fname);
+ * void free_texture(Texture *tex);
+ *
+ * DataSet<Texture*> texman(load_texture, free_texture);
+ * Texture *foo = texman.get("foo.png");
+ * \endcode
+ */
+#ifndef DATASET_H_
+#define DATASET_H_
+
+#include <string>
+#include <map>
+#include <resman.h>
+
+template <typename T>
+class DataSet {
+protected:
+       mutable std::map<std::string, T> data;
+       mutable struct resman *rman;
+
+       T (*create)();
+       bool (*load)(T, const char*);
+       bool (*done)(T);
+       void (*destroy)(T);
+
+       static int dataset_load_func(const char *fname, int id, void *cls);
+       static int dataset_done_func(int id, void *cls);
+       static void dataset_destroy_func(int id, void *cls);
+
+public:
+       DataSet(T (*create_func)(), bool (*load_func)(T, const char*), bool (*done_func)(T) = 0, void (*destr_func)(T) = 0);
+       ~DataSet();
+
+       void clear();
+       void update();
+
+       T get(const char *name) const;
+};
+
+#include "dataset.inl"
+
+#endif // DATASET_H_
diff --git a/src/dataset.inl b/src/dataset.inl
new file mode 100644 (file)
index 0000000..ca01ea2
--- /dev/null
@@ -0,0 +1,99 @@
+#include <stdio.h>
+#include <string.h>
+#include "logger.h"
+
+template <typename T>
+DataSet<T>::DataSet(T (*create_func)(), bool (*load_func)(T, const char*), bool (*done_func)(T), void (*destr_func)(T))
+{
+       create = create_func;
+       load = load_func;
+       done = done_func;
+       destroy = destr_func;
+
+       rman = resman_create();
+       resman_set_load_func(rman, dataset_load_func, this);
+       resman_set_done_func(rman, dataset_done_func, this);
+       resman_set_destroy_func(rman, dataset_destroy_func, this);
+}
+
+template <typename T>
+DataSet<T>::~DataSet()
+{
+       resman_free(rman);
+}
+
+template <typename T>
+void DataSet<T>::clear()
+{
+       resman_free(rman);
+       data.clear();
+
+       rman = resman_create();
+}
+
+template <typename T>
+void DataSet<T>::update()
+{
+       resman_poll(rman);
+}
+
+template <typename T>
+T DataSet<T>::get(const char *name) const
+{
+       typename std::map<std::string, T>::const_iterator iter = data.find(name);
+       if(iter != data.end()) {
+               return iter->second;
+       }
+
+       T res = create();
+       data[name] = res;
+       resman_lookup(rman, name, res);
+       return res;
+}
+
+
+// --- static functions to pass as callback to resman ---
+
+template <typename T>
+int DataSet<T>::dataset_load_func(const char *fname, int id, void *cls)
+{
+       DataSet<T> *dset = (DataSet<T>*)cls;
+       T data = (T)resman_get_res_data(dset->rman, id);
+       if(!data) return -1;
+
+       if(!dset->load(data, fname)) {
+               return -1;
+       }
+       return 0;
+}
+
+template <typename T>
+int DataSet<T>::dataset_done_func(int id, void *cls)
+{
+       DataSet<T> *dset = (DataSet<T>*)cls;
+
+       T data = (T)resman_get_res_data(dset->rman, id);
+       int load_res = resman_get_res_result(dset->rman, id);
+
+       if(load_res != 0) {
+               error_log("failed to load resource %d (%s)\n", id, resman_get_res_name(dset->rman, id));
+       } else {
+               info_log("done loading resource %d (%s)\n", id, resman_get_res_name(dset->rman, id));
+       }
+
+       if(dset->done) {
+               dset->done(data);
+       }
+       return 0;
+}
+
+template <typename T>
+void DataSet<T>::dataset_destroy_func(int id, void *cls)
+{
+       DataSet<T> *dset = (DataSet<T>*)cls;
+       T data = (T)resman_get_res_data(dset->rman, id);
+
+       if(dset->destroy) {
+               dset->destroy(data);
+       }
+}
diff --git a/src/exhibit.cc b/src/exhibit.cc
new file mode 100644 (file)
index 0000000..8653343
--- /dev/null
@@ -0,0 +1,41 @@
+#include "exhibit.h"
+#include "snode.h"
+
+Exhibit::Exhibit()
+{
+}
+
+void *Exhibit::select(const Ray &ray) const
+{
+       return 0;       // TODO
+}
+
+void *Exhibit::select(const Sphere &sph) const
+{
+       return 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
+{
+       if(node) {
+               glMatrixMode(GL_MODELVIEW);
+               glPopMatrix();
+       }
+}
diff --git a/src/exhibit.h b/src/exhibit.h
new file mode 100644 (file)
index 0000000..c4c0ec9
--- /dev/null
@@ -0,0 +1,32 @@
+#ifndef EXHIBIT_H_
+#define EXHIBIT_H_
+
+#include <gmath/gmath.h>
+#include "object.h"
+#include "geom.h"
+
+/*
+- select me aktina kai select me sfaira, epistrefei Selection
+- hover me aktina kai hover me sfaira
+- move me selection, origin, direction kai rotation (?)
+ */
+
+class Exhibit : public Object {
+public:
+       Exhibit();
+       virtual ~Exhibit() = default;
+
+       Exhibit(const Exhibit&) = delete;
+       Exhibit &operator =(const Exhibit &) = delete;
+
+       virtual void *select(const Ray &ray) const;
+       virtual void *select(const Sphere &sph) const;
+
+       virtual void update(float dt = 0.0f);
+
+       virtual void pre_draw() const;
+       virtual void draw() const;
+       virtual void post_draw() const;
+};
+
+#endif // EXHIBIT_H_
diff --git a/src/geom.cc b/src/geom.cc
new file mode 100644 (file)
index 0000000..55cfed7
--- /dev/null
@@ -0,0 +1,294 @@
+#include <algorithm>
+#include <float.h>
+#include "geom.h"
+
+GeomObject::~GeomObject()
+{
+}
+
+
+Sphere::Sphere()
+{
+       radius = 1.0;
+}
+
+Sphere::Sphere(const Vec3 &cent, float radius)
+       : center(cent)
+{
+       this->radius = radius;
+}
+
+GeomObjectType Sphere::get_type() const
+{
+       return GOBJ_SPHERE;
+}
+
+void Sphere::set_union(const GeomObject *obj1, const GeomObject *obj2)
+{
+       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);
+}
+
+void Sphere::set_intersection(const GeomObject *obj1, const GeomObject *obj2)
+{
+       fprintf(stderr, "Sphere::intersection undefined\n");
+}
+
+bool Sphere::intersect(const Ray &ray, HitPoint *hit) const
+{
+       float a = dot(ray.dir, ray.dir);
+       float b = 2.0 * ray.dir.x * (ray.origin.x - center.x) +
+               2.0 * ray.dir.y * (ray.origin.y - center.y) +
+               2.0 * ray.dir.z * (ray.origin.z - center.z);
+       float c = dot(ray.origin, ray.origin) + dot(center, center) -
+               2.0 * dot(ray.origin, center) - radius * radius;
+
+       float discr = b * b - 4.0 * a * c;
+       if(discr < 1e-4) {
+               return false;
+       }
+
+       float sqrt_discr = sqrt(discr);
+       float t0 = (-b + sqrt_discr) / (2.0 * a);
+       float t1 = (-b - sqrt_discr) / (2.0 * a);
+
+       if(t0 < 1e-4)
+               t0 = t1;
+       if(t1 < 1e-4)
+               t1 = t0;
+
+       float t = t0 < t1 ? t0 : t1;
+       if(t < 1e-4) {
+               return false;
+       }
+
+       // fill the HitPoint structure
+       if(hit) {
+               hit->obj = this;
+               hit->dist = t;
+               hit->pos = ray.origin + ray.dir * t;
+               hit->normal = (hit->pos - center) / radius;
+       }
+       return true;
+}
+
+bool Sphere::contains(const Vec3 &pt) const
+{
+       return length_sq(pt - center) <= radius * radius;
+}
+
+float Sphere::distance(const Vec3 &v) const
+{
+       return length(v - center) - radius;
+}
+
+AABox::AABox()
+{
+}
+
+AABox::AABox(const Vec3 &vmin, const Vec3 &vmax)
+       : min(vmin), max(vmax)
+{
+}
+
+GeomObjectType AABox::get_type() const
+{
+       return GOBJ_AABOX;
+}
+
+void AABox::set_union(const GeomObject *obj1, const GeomObject *obj2)
+{
+       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);
+}
+
+void AABox::set_intersection(const GeomObject *obj1, const GeomObject *obj2)
+{
+       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];
+               }
+       }
+}
+
+bool AABox::intersect(const Ray &ray, HitPoint *hit) const
+{
+       Vec3 param[2] = {min, max};
+       Vec3 inv_dir(1.0 / ray.dir.x, 1.0 / ray.dir.y, 1.0 / ray.dir.z);
+       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;
+       float tmax = (param[1 - sign[0]].x - ray.origin.x) * inv_dir.x;
+       float tymin = (param[sign[1]].y - ray.origin.y) * inv_dir.y;
+       float tymax = (param[1 - sign[1]].y - ray.origin.y) * inv_dir.y;
+
+       if(tmin > tymax || tymin > tmax) {
+               return false;
+       }
+       if(tymin > tmin) {
+               tmin = tymin;
+       }
+       if(tymax < tmax) {
+               tmax = tymax;
+       }
+
+       float tzmin = (param[sign[2]].z - ray.origin.z) * inv_dir.z;
+       float tzmax = (param[1 - sign[2]].z - ray.origin.z) * inv_dir.z;
+
+       if(tmin > tzmax || tzmin > tmax) {
+               return false;
+       }
+       if(tzmin > tmin) {
+               tmin = tzmin;
+       }
+       if(tzmax < tmax) {
+               tmax = tzmax;
+       }
+
+       float t = tmin < 1e-4 ? tmax : tmin;
+       if(t >= 1e-4) {
+
+               if(hit) {
+                       hit->obj = this;
+                       hit->dist = t;
+                       hit->pos = ray.origin + ray.dir * t;
+
+                       float min_dist = FLT_MAX;
+                       Vec3 offs = min + (max - min) / 2.0;
+                       Vec3 local_hit = hit->pos - offs;
+
+                       static const Vec3 axis[] = {
+                               Vec3(1, 0, 0), Vec3(0, 1, 0), Vec3(0, 0, 1)
+                       };
+                       //int tcidx[][2] = {{2, 1}, {0, 2}, {0, 1}};
+
+                       for(int i=0; i<3; i++) {
+                               float dist = fabs((max[i] - offs[i]) - fabs(local_hit[i]));
+                               if(dist < min_dist) {
+                                       min_dist = dist;
+                                       hit->normal = axis[i] * (local_hit[i] < 0.0 ? 1.0 : -1.0);
+                                       //hit->texcoord = Vec2(hit->pos[tcidx[i][0]], hit->pos[tcidx[i][1]]);
+                               }
+                       }
+               }
+               return true;
+       }
+       return false;
+}
+
+bool AABox::contains(const Vec3 &v) const
+{
+       return v.x >= min.x && v.y >= min.y && v.z >= min.z &&
+               v.x <= max.x && v.y <= max.y && v.z <= max.z;
+}
+
+float AABox::distance(const Vec3 &v) const
+{
+       return 0.0;     // TODO
+}
+
+
+Plane::Plane()
+       : normal(0.0, 1.0, 0.0)
+{
+}
+
+Plane::Plane(const Vec3 &p, const Vec3 &norm)
+       : pt(p)
+{
+       normal = normalize(norm);
+}
+
+Plane::Plane(const Vec3 &p1, const Vec3 &p2, const Vec3 &p3)
+       : pt(p1)
+{
+       normal = normalize(cross(p2 - p1, p3 - p1));
+}
+
+Plane::Plane(const Vec3 &normal, float dist)
+{
+       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);
+       if(fabs(ndotdir) < 1e-4) {
+               return false;
+       }
+
+       if(hit) {
+               Vec3 ptdir = pt - ray.origin;
+               float t = dot(normal, ptdir) / ndotdir;
+
+               hit->dist = t;
+               hit->pos = ray.origin + ray.dir * t;
+               hit->normal = normal;
+               hit->obj = this;
+       }
+       return true;
+}
+
+bool Plane::contains(const Vec3 &v) const
+{
+       return dot(v, normal) <= 0.0;
+}
+
+float Plane::distance(const Vec3 &v) const
+{
+       return dot(v - pt, normal);
+}
diff --git a/src/geom.h b/src/geom.h
new file mode 100644 (file)
index 0000000..e110193
--- /dev/null
@@ -0,0 +1,95 @@
+#ifndef GEOMOBJ_H_
+#define GEOMOBJ_H_
+
+#include <gmath/gmath.h>
+
+enum GeomObjectType {
+       GOBJ_UNKNOWN,
+       GOBJ_SPHERE,
+       GOBJ_AABOX,
+       GOBJ_PLANE
+};
+
+class GeomObject;
+
+struct HitPoint {
+       float dist;                     // parametric distance along the ray
+       Vec3 pos;                       // position of intersection (orig + dir * dist)
+       Vec3 normal;            // normal at the point of intersection
+       Ray ray, local_ray;
+       const GeomObject *obj;  // pointer to the intersected geom-object
+       void *data;                     // place to hang extra data
+};
+
+class GeomObject {
+public:
+       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 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;
+};
+
+class Sphere : public GeomObject {
+public:
+       Vec3 center;
+       float radius;
+
+       Sphere();
+       Sphere(const Vec3 &center, float radius);
+
+       GeomObjectType get_type() const;
+
+       void set_union(const GeomObject *obj1, const GeomObject *obj2);
+       void set_intersection(const GeomObject *obj1, const GeomObject *obj2);
+
+       bool intersect(const Ray &ray, HitPoint *hit = 0) const;
+       bool contains(const Vec3 &pt) const;
+
+       float distance(const Vec3 &v) const;
+};
+
+class AABox : public GeomObject {
+public:
+       Vec3 min, max;
+
+       AABox();
+       AABox(const Vec3 &min, const Vec3 &max);
+
+       GeomObjectType get_type() const;
+
+       void set_union(const GeomObject *obj1, const GeomObject *obj2);
+       void set_intersection(const GeomObject *obj1, const GeomObject *obj2);
+
+       bool intersect(const Ray &ray, HitPoint *hit = 0) const;
+       bool contains(const Vec3 &pt) const;
+
+       float distance(const Vec3 &v) const;
+};
+
+class Plane : public GeomObject {
+public:
+       Vec3 pt, normal;
+
+       Plane();
+       Plane(const Vec3 &pt, const Vec3 &normal);
+       Plane(const Vec3 &p1, const Vec3 &p2, const Vec3 &p3);
+       Plane(const Vec3 &normal, float dist);
+
+       GeomObjectType get_type() const;
+
+       void set_union(const GeomObject *obj1, const GeomObject *obj2);
+       void set_intersection(const GeomObject *obj1, const GeomObject *obj2);
+
+       bool intersect(const Ray &ray, HitPoint *hit = 0) const;
+       bool contains(const Vec3 &pt) const;
+
+       float distance(const Vec3 &v) const;
+};
+
+#endif // GEOMOBJ_H_
diff --git a/src/image.cc b/src/image.cc
new file mode 100644 (file)
index 0000000..e79073d
--- /dev/null
@@ -0,0 +1,299 @@
+#include <string.h>
+
+#ifndef _MSC_VER
+#include <alloca.h>
+#else
+#include <malloc.h>
+#endif
+
+#include "imago2.h"
+#include "image.h"
+
+static int pixel_elements(Image::Format fmt);
+static int elem_size(Image::Format fmt);
+static int pixel_size(Image::Format fmt);
+
+Image::Image()
+{
+       fmt = FMT_RGBA;
+       width = height = 0;
+       pixels = 0;
+}
+
+Image::~Image()
+{
+       delete [] (char*)pixels;
+}
+
+int Image::get_width() const
+{
+       return width;
+}
+
+int Image::get_height() const
+{
+       return height;
+}
+
+Image::Format Image::get_format() const
+{
+       return fmt;
+}
+
+bool Image::create(int x, int y, Format fmt)
+{
+       width = x;
+       height = y;
+       this->fmt = fmt;
+
+       try {
+               pixels = new char[x * y * pixel_size(fmt)];
+       }
+       catch(...) {
+               return false;
+       }
+       return true;
+}
+
+bool Image::set_pixels(int xsz, int ysz, void *pixels, Format fmt)
+{
+       if(!create(xsz, ysz, fmt)) {
+               return false;
+       }
+       memcpy(this->pixels, pixels, xsz * ysz * pixel_size(fmt));
+       return true;
+}
+
+bool Image::set_pixels(int xsz, int ysz, void *pixels, int scan_width, Format fmt)
+{
+       return set_pixels(xsz, ysz, pixels, 0, 0, scan_width, fmt);
+}
+
+bool Image::set_pixels(int xsz, int ysz, void *pixels, int x, int y, int scan_width, Format fmt)
+{
+       if(scan_width <= 0) {
+               scan_width = xsz;
+       }
+
+       if(!create(xsz, ysz, fmt)) {
+               return false;
+       }
+
+       int pixsz = pixel_size(fmt);
+
+       unsigned char *dest = (unsigned char*)this->pixels;
+       unsigned char *src = (unsigned char*)pixels + (y * scan_width + x) * pixsz;
+       for(int i=0; i<ysz; i++) {
+               memcpy(dest, src, xsz * pixsz);
+               dest += xsz * pixsz;
+               src += scan_width * pixsz;
+       }
+       return true;
+}
+
+void *Image::get_pixels() const
+{
+       return pixels;
+}
+
+void Image::flip_horizontal()
+{
+       int pixsz = pixel_size(fmt);
+
+       unsigned char *tmppix = (unsigned char*)alloca(pixsz);
+
+       unsigned char *scan = (unsigned char*)pixels;
+       for(int i=0; i<height; i++) {
+               unsigned char *dest = scan;
+               unsigned char *src = scan + (width - 1) * pixsz;
+
+               while(src > dest) {
+                       memcpy(tmppix, src, pixsz);
+                       memcpy(src, dest, pixsz);
+                       memcpy(dest, tmppix, pixsz);
+                       dest += pixsz;
+                       src -= pixsz;
+               }
+
+               scan += width * pixsz;
+       }
+}
+
+void Image::flip_vertical()
+{
+       int pixsz = pixel_size(fmt);
+
+       unsigned char *tmpscan = (unsigned char*)alloca(width * pixsz);
+
+       unsigned char *dest = (unsigned char*)pixels;
+       unsigned char *src = (unsigned char*)pixels + (height - 1) * width * pixsz;
+
+       while(src > dest) {
+               memcpy(tmpscan, src, width * pixsz);
+               memcpy(src, dest, width * pixsz);
+               memcpy(dest, tmpscan, width * pixsz);
+               dest += width * pixsz;
+               src -= width * pixsz;
+       }
+}
+
+void Image::rotate_180()
+{
+       flip_vertical();
+       flip_horizontal();
+}
+
+void Image::resize_half()
+{
+       int pixsz = pixel_size(fmt);
+       int newxsz = width / 2;
+       int newysz = height / 2;
+
+       if(!newxsz || !newysz) return;
+
+       unsigned char *newpix = new unsigned char[newxsz * newysz * pixsz];
+
+       unsigned char *sptr = (unsigned char*)pixels;
+       unsigned char *dptr = newpix;
+
+       for(int i=0; i<newysz; i++) {
+               if(i & 1) sptr += width * pixsz;
+               for(int j=0; j<newxsz; j++) {
+                       memcpy(dptr, sptr, pixsz);
+                       dptr += pixsz;
+                       sptr += pixsz * 2;
+               }
+       }
+
+       delete [] (char*)pixels;
+       pixels = newpix;
+       width = newxsz;
+       height = newysz;
+}
+
+bool Image::load(const char *fname)
+{
+       struct img_pixmap pixmap;
+
+       img_init(&pixmap);
+       if(img_load(&pixmap, fname) == -1) {
+               return false;
+       }
+
+       Format fmt;
+       switch(pixmap.fmt) {
+       case IMG_FMT_GREY8:
+               fmt = FMT_GREY;
+               break;
+       case IMG_FMT_RGB24:
+               fmt = FMT_RGB;
+               break;
+       case IMG_FMT_RGBA32:
+               fmt = FMT_RGBA;
+               break;
+       case IMG_FMT_GREYF:
+               fmt = FMT_GREY_FLOAT;
+               break;
+       case IMG_FMT_RGBF:
+               fmt = FMT_RGB_FLOAT;
+               break;
+       case IMG_FMT_RGBAF:
+               fmt = FMT_RGBA_FLOAT;
+               break;
+       default:
+               img_destroy(&pixmap);
+               return false;
+       }
+
+       if(!set_pixels(pixmap.width, pixmap.height, pixmap.pixels, fmt)) {
+               img_destroy(&pixmap);
+               return false;
+       }
+       img_destroy(&pixmap);
+       return true;
+}
+
+bool Image::save(const char *fname) const
+{
+       struct img_pixmap pixmap;
+
+       img_init(&pixmap);
+
+       switch(fmt) {
+       case FMT_GREY:
+               pixmap.fmt = IMG_FMT_GREY8;
+               break;
+       case FMT_GREY_FLOAT:
+               pixmap.fmt = IMG_FMT_GREYF;
+               break;
+       case FMT_RGB:
+               pixmap.fmt = IMG_FMT_RGB24;
+               break;
+       case FMT_RGB_FLOAT:
+               pixmap.fmt = IMG_FMT_RGBF;
+               break;
+       case FMT_RGBA:
+               pixmap.fmt = IMG_FMT_RGBA32;
+               break;
+       case FMT_RGBA_FLOAT:
+               pixmap.fmt = IMG_FMT_RGBAF;
+               break;
+       default:
+               return false;
+       }
+
+       pixmap.width = width;
+       pixmap.height = height;
+       pixmap.pixels = pixels;
+       pixmap.pixelsz = pixel_size(fmt);
+
+       if(img_save(&pixmap, fname) == -1) {
+               return false;
+       }
+       return true;
+}
+
+static int pixel_elements(Image::Format fmt)
+{
+       switch(fmt) {
+       case Image::FMT_GREY:
+       case Image::FMT_GREY_FLOAT:
+               return 1;
+
+       case Image::FMT_RGB:
+       case Image::FMT_RGB_FLOAT:
+               return 3;
+
+       case Image::FMT_RGBA:
+       case Image::FMT_RGBA_FLOAT:
+               return 4;
+
+       default:
+               break;
+       }
+       return 0;
+}
+
+static int elem_size(Image::Format fmt)
+{
+       switch(fmt) {
+       case Image::FMT_GREY:
+       case Image::FMT_RGB:
+       case Image::FMT_RGBA:
+               return 1;
+
+       case Image::FMT_GREY_FLOAT:
+       case Image::FMT_RGB_FLOAT:
+       case Image::FMT_RGBA_FLOAT:
+               return sizeof(float);
+
+       default:
+               break;
+       }
+       return 0;
+}
+
+static int pixel_size(Image::Format fmt)
+{
+       return elem_size(fmt) * pixel_elements(fmt);
+}
diff --git a/src/image.h b/src/image.h
new file mode 100644 (file)
index 0000000..1d16979
--- /dev/null
@@ -0,0 +1,49 @@
+#ifndef IMAGE_H_
+#define IMAGE_H_
+
+#include <string>
+
+class Image {
+public:
+       enum Format {
+               FMT_GREY,
+               FMT_RGB,
+               FMT_RGBA,
+               FMT_GREY_FLOAT,
+               FMT_RGB_FLOAT,
+               FMT_RGBA_FLOAT
+       };
+
+private:
+       Format fmt;
+       int width, height;
+       void *pixels;
+
+public:
+       std::string name;
+
+       Image();
+       ~Image();
+
+       int get_width() const;
+       int get_height() const;
+
+       Format get_format() const;
+
+       bool create(int x, int y, Format fmt = FMT_RGBA);
+       bool set_pixels(int xsz, int ysz, void *pixels, Format fmt = FMT_RGBA);
+       bool set_pixels(int xsz, int ysz, void *pixels, int scan_width, Format fmt = FMT_RGBA);
+       bool set_pixels(int xsz, int ysz, void *pixels, int x, int y, int scan_width = -1, Format fmt = FMT_RGBA);
+       void *get_pixels() const;
+
+       void flip_horizontal();
+       void flip_vertical();
+       void rotate_180();
+
+       void resize_half();
+
+       bool load(const char *fname);
+       bool save(const char *fname) const;
+};
+
+#endif // IMAGE_H_
diff --git a/src/logger.cc b/src/logger.cc
new file mode 100644 (file)
index 0000000..015c9bd
--- /dev/null
@@ -0,0 +1,129 @@
+#include <stdio.h>
+#include <stdarg.h>
+#include "logger.h"
+#include "ui.h"
+
+#if defined(unix) || defined(__unix__) || defined(__APPLE__)
+#include <unistd.h>
+#elif defined(WIN32)
+#include <windows.h>
+#endif
+
+#define UI_MSG_TIMEOUT 4000
+
+enum { LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_FATAL, LOG_DEBUG };
+
+static int typecolor(int type);
+
+static FILE *fp = stdout;
+
+static void logmsg(int type, const char *fmt, va_list ap)
+{
+#if defined(unix) || defined(__unix__) || (defined(__APPLE__) && !defined(TARGET_IPHONE))
+       if(isatty(fileno(fp)) && type != LOG_INFO) {
+               int c = typecolor(type);
+               fprintf(fp, "\033[%dm", c);
+               vfprintf(fp, fmt, ap);
+               fprintf(fp, "\033[0m");
+       } else
+#endif
+       {
+               vfprintf(fp, fmt, ap);
+       }
+       if(type == LOG_ERROR || type == LOG_FATAL || type == LOG_DEBUG) {
+               fflush(fp);
+       }
+
+#ifdef WIN32
+       if(type == LOG_FATAL) {
+               static char msgbuf[1024];
+               vsnprintf(msgbuf, sizeof msgbuf - 1, fmt, ap);
+               msgbuf[sizeof msgbuf - 1] = 0;
+               MessageBox(0, msgbuf, "Fatal error", MB_OK | MB_ICONSTOP);
+       }
+#endif
+}
+
+extern "C" void info_log(const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       logmsg(LOG_INFO, fmt, ap);
+       va_end(ap);
+}
+
+extern "C" void warning_log(const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       logmsg(LOG_WARNING, fmt, ap);
+       va_end(ap);
+
+       va_start(ap, fmt);
+       show_messagev(UI_MSG_TIMEOUT, Vec3(1.0, 0.8, 0.1), fmt, ap);
+       va_end(ap);
+}
+
+extern "C" void error_log(const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       logmsg(LOG_ERROR, fmt, ap);
+       va_end(ap);
+
+       va_start(ap, fmt);
+       show_messagev(UI_MSG_TIMEOUT, Vec3(1.0, 0.1, 0.1), fmt, ap);
+       va_end(ap);
+}
+
+extern "C" void fatal_log(const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       logmsg(LOG_FATAL, fmt, ap);
+       va_end(ap);
+}
+
+extern "C" void debug_log(const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       logmsg(LOG_DEBUG, fmt, ap);
+       va_end(ap);
+}
+
+enum {
+       BLACK = 0,
+       RED,
+       GREEN,
+       YELLOW,
+       BLUE,
+       MAGENTA,
+       CYAN,
+       WHITE
+};
+
+#define ANSI_FGCOLOR(x)        (30 + (x))
+#define ANSI_BGCOLOR(x)        (40 + (x))
+
+static int typecolor(int type)
+{
+       switch(type) {
+       case LOG_ERROR:
+               return ANSI_FGCOLOR(RED);
+       case LOG_FATAL:
+               return ANSI_FGCOLOR(RED);       // TODO differentiate from LOG_ERROR
+       case LOG_WARNING:
+               return ANSI_FGCOLOR(YELLOW);
+       case LOG_DEBUG:
+               return ANSI_FGCOLOR(MAGENTA);
+       default:
+               break;
+       }
+       return 37;
+}
diff --git a/src/logger.h b/src/logger.h
new file mode 100644 (file)
index 0000000..978d634
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef LOGGER_H_
+#define LOGGER_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void info_log(const char *fmt, ...);
+void warning_log(const char *fmt, ...);
+void error_log(const char *fmt, ...);
+void fatal_log(const char *fmt, ...);
+void debug_log(const char *fmt, ...);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // LOGGER_H_
diff --git a/src/main.cc b/src/main.cc
new file mode 100644 (file)
index 0000000..eff77a5
--- /dev/null
@@ -0,0 +1,226 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <GL/glew.h>
+#include <SDL2/SDL.h>
+#include "app.h"
+
+static bool init(int argc, char **argv);
+static void process_event(SDL_Event *ev);
+static void proc_modkeys();
+
+static SDL_Window *win;
+static SDL_GLContext ctx;
+static bool fullscreen, mouse_grabbed;
+static bool quit;
+
+static unsigned int start_time;
+static unsigned int modkeys;
+
+SDL_GameController *gamepad;
+
+static int scale_factor = 1;
+
+int main(int argc, char **argv)
+{
+       if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) {
+               fprintf(stderr, "failed to initialize SDL\n");
+               return 1;
+       }
+
+       SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
+       SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 8);
+       SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, 1);
+
+       int defpos = SDL_WINDOWPOS_UNDEFINED;
+       unsigned int sdlflags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
+
+       if(!(win = SDL_CreateWindow("demo", defpos, defpos, 1024, 768, sdlflags))) {
+               // try again without sRGB capability
+               SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, 0);
+               if(!(win = SDL_CreateWindow("demo", defpos, defpos, 1024, 768, sdlflags))) {
+                       fprintf(stderr, "failed to create window\n");
+                       SDL_Quit();
+                       return 1;
+               }
+               fprintf(stderr, "failed to get an sRGB framebuffer.\n");
+       }
+       int val;
+       SDL_GL_GetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, &val);
+       printf("SDL says we %s an sRGB framebuffer\n", val ? "got" : "didn't get");
+
+       if(!(ctx = SDL_GL_CreateContext(win))) {
+               fprintf(stderr, "failed to create OpenGL context\n");
+               SDL_Quit();
+               return 1;
+       }
+       SDL_GL_GetDrawableSize(win, &win_width, &win_height);
+       win_aspect = (float)win_width / (float)win_height;
+
+       printf("detected %d joysticks\n", SDL_NumJoysticks());
+       for(int i=0; i<SDL_NumJoysticks(); i++) {
+               if(SDL_IsGameController(i)) {
+                       if(!(gamepad = SDL_GameControllerOpen(i))) {
+                               fprintf(stderr, "failed to open game controller %i: %s\n", i, SDL_GetError());
+                               continue;
+                       }
+                       printf("Using gamepad: %s\n", SDL_GameControllerNameForIndex(i));
+               }
+       }
+
+       if(!init(argc, argv)) {
+               SDL_Quit();
+               return 1;
+       }
+       app_reshape(win_width, win_height);
+
+       while(!quit) {
+               SDL_Event ev;
+
+               time_msec = SDL_GetTicks() - start_time;
+               while(SDL_PollEvent(&ev)) {
+                       process_event(&ev);
+                       if(quit) goto break_evloop;
+               }
+
+               app_display();
+       }
+break_evloop:
+
+       app_cleanup();
+       SDL_Quit();
+       return 0;
+}
+
+void app_swap_buffers()
+{
+       SDL_GL_SwapWindow(win);
+}
+
+void app_quit()
+{
+       quit = true;
+}
+
+unsigned int app_get_modifiers()
+{
+       return modkeys;
+}
+
+void app_resize(int x, int y)
+{
+       SDL_SetWindowSize(win, x, y);
+}
+
+void app_fullscreen(bool fs)
+{
+       SDL_SetWindowFullscreen(win, fs ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
+       fullscreen = fs;
+}
+
+void app_toggle_fullscreen()
+{
+       app_fullscreen(!fullscreen);
+}
+
+bool app_is_fullscreen()
+{
+       return fullscreen;
+}
+
+void app_grab_mouse(bool grab)
+{
+       if(grab) {
+               SDL_WarpMouseInWindow(win, win_width / 2, win_height / 2);
+       }
+       //SDL_SetWindowGrab(win, grab ? SDL_TRUE : SDL_FALSE);
+       //SDL_ShowCursor(grab ? 1 : 0);
+       SDL_SetRelativeMouseMode(grab ? SDL_TRUE : SDL_FALSE);
+       mouse_grabbed = grab;
+}
+
+void app_toggle_grab_mouse()
+{
+       app_grab_mouse(!mouse_grabbed);
+}
+
+bool app_is_mouse_grabbed()
+{
+       return mouse_grabbed;
+}
+
+
+static bool init(int argc, char **argv)
+{
+       glewInit();
+
+       if(!app_init(argc, argv)) {
+               return false;
+       }
+
+       start_time = SDL_GetTicks();
+       return true;
+}
+
+static void process_event(SDL_Event *ev)
+{
+       switch(ev->type) {
+       case SDL_QUIT:
+               quit = true;
+               break;
+
+       case SDL_KEYDOWN:
+       case SDL_KEYUP:
+               proc_modkeys();
+               app_keyboard(ev->key.keysym.sym, ev->key.state == SDL_PRESSED);
+               break;
+
+       case SDL_MOUSEBUTTONDOWN:
+       case SDL_MOUSEBUTTONUP:
+               proc_modkeys();
+               app_mouse_button(ev->button.button - SDL_BUTTON_LEFT, ev->button.state == SDL_PRESSED,
+                               ev->button.x * scale_factor, ev->button.y * scale_factor);
+               break;
+
+       case SDL_MOUSEMOTION:
+               if(mouse_grabbed) {
+                       app_mouse_delta(ev->motion.xrel, ev->motion.yrel);
+               } else {
+                       app_mouse_motion(ev->motion.x * scale_factor, ev->motion.y * scale_factor);
+               }
+               break;
+
+       case SDL_WINDOWEVENT:
+               if(ev->window.event == SDL_WINDOWEVENT_RESIZED) {
+                       SDL_GL_GetDrawableSize(win, &win_width, &win_height);
+                       win_aspect = (float)win_width / (float)win_height;
+                       scale_factor = win_width / ev->window.data1;
+                       app_reshape(win_width, win_height);
+               }
+               break;
+
+       case SDL_CONTROLLERAXISMOTION:
+               app_gamepad_axis(ev->caxis.axis, ev->caxis.value / 32768.0f);
+               break;
+
+       case SDL_CONTROLLERBUTTONDOWN:
+       case SDL_CONTROLLERBUTTONUP:
+               app_gamepad_button(ev->cbutton.button, ev->type == SDL_CONTROLLERBUTTONDOWN);
+               break;
+       }
+}
+
+static void proc_modkeys()
+{
+       modkeys = 0;
+       SDL_Keymod sdlmod = SDL_GetModState();
+       if(sdlmod & KMOD_SHIFT) {
+               modkeys |= MOD_SHIFT;
+       }
+       if(sdlmod & KMOD_ALT) {
+               modkeys |= MOD_ALT;
+       }
+       if(sdlmod & KMOD_CTRL) {
+               modkeys |= MOD_CTRL;
+       }
+}
diff --git a/src/material.cc b/src/material.cc
new file mode 100644 (file)
index 0000000..189b6ad
--- /dev/null
@@ -0,0 +1,95 @@
+#include <algorithm>
+#include <string.h>
+#include "opengl.h"
+#include "material.h"
+#include "sdr.h"
+#include "app.h"
+
+Material::Material()
+       : diffuse(1.0f, 1.0f, 1.0f)
+{
+       shininess = 0.0f;
+       alpha = 1.0f;
+       memset(stdtex, 0, sizeof stdtex);
+}
+
+void Material::setup() const
+{
+       float kd[] = {diffuse.x, diffuse.y, diffuse.z, alpha};
+       float ks[] = {specular.x, specular.y, specular.z, 1.0f};
+
+       glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, kd);
+       glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, ks);
+       glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess);
+
+       int ntex = std::min((int)textures.size(), 8);   // TODO: use max texture units
+       for(int i=0; i<ntex; i++) {
+               bind_texture(textures[i], i);
+       }
+
+       /*
+       if(stdtex[MTL_TEX_LIGHTMAP]) {
+               bind_program(stdtex[MTL_TEX_DIFFUSE] ? sdr_ltmap : sdr_ltmap_notex);
+       }
+       */
+}
+
+void Material::add_texture(Texture *tex, int type)
+{
+       if(std::find(textures.begin(), textures.end(), tex) == textures.end()) {
+               textures.push_back(tex);
+       }
+
+       if(type != MTL_TEX_UNKNOWN) {
+               stdtex[type] = tex;
+       }
+}
+
+void Material::remove_texture(Texture *tex)
+{
+       std::vector<Texture*>::iterator it = std::find(textures.begin(), textures.end(), tex);
+       if(it != textures.end()) {
+               textures.erase(it);
+       }
+
+       for(int i=0; i<NUM_MTL_TEXTURES; i++) {
+               if(stdtex[i] == tex) {
+                       stdtex[i] = 0;
+               }
+       }
+}
+
+int mtl_parse_type(const char *str)
+{
+       if(strcmp(str, "diffuse") == 0) {
+               return MTL_TEX_DIFFUSE;
+       } else if(strcmp(str, "specular") == 0) {
+               return MTL_TEX_SPECULAR;
+       } else if(strcmp(str, "normalmap") == 0) {
+               return MTL_TEX_NORMALMAP;
+       } else if(strcmp(str, "lightmap") == 0) {
+               return MTL_TEX_LIGHTMAP;
+       } else if(strcmp(str, "envmap") == 0) {
+               return MTL_TEX_ENVMAP;
+       }
+       return MTL_TEX_UNKNOWN;
+}
+
+const char *mtl_type_string(int type)
+{
+       switch(type) {
+       case MTL_TEX_DIFFUSE:
+               return "diffuse";
+       case MTL_TEX_SPECULAR:
+               return "specular";
+       case MTL_TEX_NORMALMAP:
+               return "normalmap";
+       case MTL_TEX_LIGHTMAP:
+               return "lightmap";
+       case MTL_TEX_ENVMAP:
+               return "envmap";
+       default:
+               break;
+       }
+       return "unknown";
+}
diff --git a/src/material.h b/src/material.h
new file mode 100644 (file)
index 0000000..c46a21d
--- /dev/null
@@ -0,0 +1,43 @@
+#ifndef MATERIAL_H_
+#define MATERIAL_H_
+
+#include <vector>
+#include <string>
+#include <gmath/gmath.h>
+#include "texture.h"
+
+enum {
+       MTL_TEX_DIFFUSE,
+       MTL_TEX_SPECULAR,
+       MTL_TEX_NORMALMAP,
+       MTL_TEX_LIGHTMAP,
+       MTL_TEX_ENVMAP,
+
+       MTL_TEX_UNKNOWN
+};
+
+#define NUM_MTL_TEXTURES       MTL_TEX_UNKNOWN
+
+class Material {
+public:
+       std::string name;
+       Vec3 diffuse, specular;
+       float shininess;
+       float alpha;
+
+       Texture *stdtex[NUM_MTL_TEXTURES];
+       std::vector<Texture*> textures;
+
+       Material();
+       void setup() const;
+
+       void add_texture(Texture *tex, int type = MTL_TEX_UNKNOWN);
+       void remove_texture(Texture *tex);
+};
+
+// returns MTL_TEX_whatever by name
+int mtl_parse_type(const char *str);
+// returns the name of a material type
+const char *mtl_type_string(int type);
+
+#endif // MATERIAL_H_
diff --git a/src/mech_exhibit.h b/src/mech_exhibit.h
new file mode 100644 (file)
index 0000000..74fa613
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef MECH_EXHIBIT_H_
+#define MECH_EXHIBIT_H_
+
+#include "exhibit.h"
+#include "machine.h"
+
+class MechExhibit : public Exhibit {
+private:
+
+public:
+       MechExhibit();
+       ~MechExhibit();
+
+       void update(float dt);
+       void draw() const;
+};
+
+#endif // MECH_EXHIBIT_H_
diff --git a/src/mesh.cc b/src/mesh.cc
new file mode 100644 (file)
index 0000000..6a1faa7
--- /dev/null
@@ -0,0 +1,1443 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <float.h>
+#include <assert.h>
+#include "opengl.h"
+#include "mesh.h"
+#include "logger.h"
+//#include "xform_node.h"
+
+#define USE_OLDGL
+
+bool Mesh::use_custom_sdr_attr = true;
+int Mesh::global_sdr_loc[NUM_MESH_ATTR] = { 0, 1, 2, 3, 4, 5, 6, 7 };
+unsigned int Mesh::intersect_mode = ISECT_DEFAULT;
+float Mesh::vertex_sel_dist = 0.01;
+float Mesh::vis_vecsize = 1.0;
+
+Mesh::Mesh()
+{
+       clear();
+
+       glGenBuffers(NUM_MESH_ATTR + 1, buffer_objects);
+
+       for(int i=0; i<NUM_MESH_ATTR; i++) {
+               vattr[i].vbo = buffer_objects[i];
+       }
+       ibo = buffer_objects[NUM_MESH_ATTR];
+       wire_ibo = 0;
+}
+
+Mesh::~Mesh()
+{
+       glDeleteBuffers(NUM_MESH_ATTR + 1, buffer_objects);
+
+       if(wire_ibo) {
+               glDeleteBuffers(1, &wire_ibo);
+       }
+}
+
+Mesh::Mesh(const Mesh &rhs)
+{
+       clear();
+
+       glGenBuffers(NUM_MESH_ATTR + 1, buffer_objects);
+
+       for(int i=0; i<NUM_MESH_ATTR; i++) {
+               vattr[i].vbo = buffer_objects[i];
+       }
+       ibo = buffer_objects[NUM_MESH_ATTR];
+       wire_ibo = 0;
+
+       clone(rhs);
+}
+
+Mesh &Mesh::operator =(const Mesh &rhs)
+{
+       if(&rhs != this) {
+               clone(rhs);
+       }
+       return *this;
+}
+
+bool Mesh::clone(const Mesh &m)
+{
+       clear();
+
+       for(int i=0; i<NUM_MESH_ATTR; i++) {
+               if(m.has_attrib(i)) {
+                       m.get_attrib_data(i);   // force validation of the actual data on the source mesh
+
+                       vattr[i].nelem = m.vattr[i].nelem;
+                       vattr[i].data = m.vattr[i].data;        // copy the actual data
+                       vattr[i].data_valid = true;
+               }
+       }
+
+       if(m.is_indexed()) {
+               m.get_index_data();             // again, force validation
+
+               // copy the index data
+               idata = m.idata;
+               idata_valid = true;
+       }
+
+       name = m.name;
+       nverts = m.nverts;
+       nfaces = m.nfaces;
+
+       //bones = m.bones;
+
+       memcpy(cur_val, m.cur_val, sizeof cur_val);
+
+       aabb = m.aabb;
+       aabb_valid = m.aabb_valid;
+       bsph = m.bsph;
+       bsph_valid = m.bsph_valid;
+
+       hitface = m.hitface;
+       hitvert = m.hitvert;
+
+       intersect_mode = m.intersect_mode;
+       vertex_sel_dist = m.vertex_sel_dist;
+       vis_vecsize = m.vis_vecsize;
+
+       return true;
+}
+
+void Mesh::set_name(const char *name)
+{
+       this->name = name;
+}
+
+const char *Mesh::get_name() const
+{
+       return name.c_str();
+}
+
+bool Mesh::has_attrib(int attr) const
+{
+       if(attr < 0 || attr >= NUM_MESH_ATTR) {
+               return false;
+       }
+
+       // if neither of these is valid, then nobody has set this attribute
+       return vattr[attr].vbo_valid || vattr[attr].data_valid;
+}
+
+bool Mesh::is_indexed() const
+{
+       return ibo_valid || idata_valid;
+}
+
+void Mesh::clear()
+{
+       //bones.clear();
+
+       for(int i=0; i<NUM_MESH_ATTR; i++) {
+               vattr[i].nelem = 0;
+               vattr[i].vbo_valid = false;
+               vattr[i].data_valid = false;
+               //vattr[i].sdr_loc = -1;
+               vattr[i].data.clear();
+       }
+       ibo_valid = idata_valid = false;
+       idata.clear();
+
+       wire_ibo_valid = false;
+
+       nverts = nfaces = 0;
+
+       bsph_valid = false;
+       aabb_valid = false;
+}
+
+float *Mesh::set_attrib_data(int attrib, int nelem, unsigned int num, const float *data)
+{
+       if(attrib < 0 || attrib >= NUM_MESH_ATTR) {
+               error_log("%s: invalid attrib: %d\n", __FUNCTION__, attrib);
+               return 0;
+       }
+
+       if(nverts && num != nverts) {
+               error_log("%s: attribute count missmatch (%d instead of %d)\n", __FUNCTION__, num, nverts);
+               return 0;
+       }
+       nverts = num;
+
+       vattr[attrib].data.clear();
+       vattr[attrib].nelem = nelem;
+       vattr[attrib].data.resize(num * nelem);
+
+       if(data) {
+               memcpy(&vattr[attrib].data[0], data, num * nelem * sizeof *data);
+       }
+
+       vattr[attrib].data_valid = true;
+       vattr[attrib].vbo_valid = false;
+       return &vattr[attrib].data[0];
+}
+
+float *Mesh::get_attrib_data(int attrib)
+{
+       if(attrib < 0 || attrib >= NUM_MESH_ATTR) {
+               error_log("%s: invalid attrib: %d\n", __FUNCTION__, attrib);
+               return 0;
+       }
+
+       vattr[attrib].vbo_valid = false;
+       return (float*)((const Mesh*)this)->get_attrib_data(attrib);
+}
+
+const float *Mesh::get_attrib_data(int attrib) const
+{
+       if(attrib < 0 || attrib >= NUM_MESH_ATTR) {
+               error_log("%s: invalid attrib: %d\n", __FUNCTION__, attrib);
+               return 0;
+       }
+
+       if(!vattr[attrib].data_valid) {
+#if GL_ES_VERSION_2_0
+               error_log("%s: can't read back attrib data on CrippledGL ES\n", __FUNCTION__);
+               return 0;
+#else
+               if(!vattr[attrib].vbo_valid) {
+                       error_log("%s: unavailable attrib: %d\n", __FUNCTION__, attrib);
+                       return 0;
+               }
+
+               // local data copy is unavailable, grab the data from the vbo
+               Mesh *m = (Mesh*)this;
+               m->vattr[attrib].data.resize(nverts * vattr[attrib].nelem);
+
+               glBindBuffer(GL_ARRAY_BUFFER, vattr[attrib].vbo);
+               void *data = glMapBuffer(GL_ARRAY_BUFFER, GL_READ_ONLY);
+               memcpy(&m->vattr[attrib].data[0], data, nverts * vattr[attrib].nelem * sizeof(float));
+               glUnmapBuffer(GL_ARRAY_BUFFER);
+
+               vattr[attrib].data_valid = true;
+#endif
+       }
+
+       return &vattr[attrib].data[0];
+}
+
+void Mesh::set_attrib(int attrib, int idx, const Vec4 &v)
+{
+       float *data = get_attrib_data(attrib);
+       if(data) {
+               data += idx * vattr[attrib].nelem;
+               for(int i=0; i<vattr[attrib].nelem; i++) {
+                       data[i] = v[i];
+               }
+       }
+}
+
+Vec4 Mesh::get_attrib(int attrib, int idx) const
+{
+       Vec4 v(0.0, 0.0, 0.0, 1.0);
+       const float *data = get_attrib_data(attrib);
+       if(data) {
+               data += idx * vattr[attrib].nelem;
+               for(int i=0; i<vattr[attrib].nelem; i++) {
+                       v[i] = data[i];
+               }
+       }
+       return v;
+}
+
+int Mesh::get_attrib_count(int attrib) const
+{
+       return has_attrib(attrib) ? nverts : 0;
+}
+
+
+unsigned int *Mesh::set_index_data(int num, const unsigned int *indices)
+{
+       int nidx = nfaces * 3;
+       if(nidx && num != nidx) {
+               error_log("%s: index count mismatch (%d instead of %d)\n", __FUNCTION__, num, nidx);
+               return 0;
+       }
+       nfaces = num / 3;
+
+       idata.clear();
+       idata.resize(num);
+
+       if(indices) {
+               memcpy(&idata[0], indices, num * sizeof *indices);
+       }
+
+       idata_valid = true;
+       ibo_valid = false;
+
+       return &idata[0];
+}
+
+unsigned int *Mesh::get_index_data()
+{
+       ibo_valid = false;
+       return (unsigned int*)((const Mesh*)this)->get_index_data();
+}
+
+const unsigned int *Mesh::get_index_data() const
+{
+       if(!idata_valid) {
+#if GL_ES_VERSION_2_0
+               error_log("%s: can't read back index data in CrippledGL ES\n", __FUNCTION__);
+               return 0;
+#else
+               if(!ibo_valid) {
+                       error_log("%s: indices unavailable\n", __FUNCTION__);
+                       return 0;
+               }
+
+               // local data copy is unavailable, gram the data from the ibo
+               Mesh *m = (Mesh*)this;
+               int nidx = nfaces * 3;
+               m->idata.resize(nidx);
+
+               glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
+               void *data = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_READ_ONLY);
+               memcpy(&m->idata[0], data, nidx * sizeof(unsigned int));
+               glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
+
+               idata_valid = true;
+#endif
+       }
+
+       return &idata[0];
+}
+
+int Mesh::get_index_count() const
+{
+       return nfaces * 3;
+}
+
+void Mesh::append(const Mesh &mesh)
+{
+       unsigned int idxoffs = nverts;
+
+       if(!nverts) {
+               clone(mesh);
+               return;
+       }
+
+       nverts += mesh.nverts;
+       nfaces += mesh.nfaces;
+
+       for(int i=0; i<NUM_MESH_ATTR; i++) {
+               if(has_attrib(i) && mesh.has_attrib(i)) {
+                       // force validating the data arrays
+                       get_attrib_data(i);
+                       mesh.get_attrib_data(i);
+
+                       // append the mesh data
+                       vattr[i].data.insert(vattr[i].data.end(), mesh.vattr[i].data.begin(), mesh.vattr[i].data.end());
+               }
+       }
+
+       if(ibo_valid || idata_valid) {
+               // make index arrays valid
+               get_index_data();
+               mesh.get_index_data();
+
+               size_t orig_sz = idata.size();
+
+               idata.insert(idata.end(), mesh.idata.begin(), mesh.idata.end());
+
+               // fixup all the new indices
+               for(size_t i=orig_sz; i<idata.size(); i++) {
+                       idata[i] += idxoffs;
+               }
+       }
+
+       // fuck everything
+       wire_ibo_valid = false;
+       aabb_valid = false;
+       bsph_valid = false;
+}
+
+// assemble a complete vertex by adding all the useful attributes
+void Mesh::vertex(float x, float y, float z)
+{
+       cur_val[MESH_ATTR_VERTEX] = Vec4(x, y, z, 1.0f);
+       vattr[MESH_ATTR_VERTEX].data_valid = true;
+       vattr[MESH_ATTR_VERTEX].nelem = 3;
+
+       for(int i=0; i<NUM_MESH_ATTR; i++) {
+               if(vattr[i].data_valid) {
+                       for(int j=0; j<vattr[MESH_ATTR_VERTEX].nelem; j++) {
+                               vattr[i].data.push_back(cur_val[i][j]);
+                       }
+               }
+               vattr[i].vbo_valid = false;
+       }
+
+       if(idata_valid) {
+               idata.clear();
+       }
+       ibo_valid = idata_valid = false;
+}
+
+void Mesh::normal(float nx, float ny, float nz)
+{
+       cur_val[MESH_ATTR_NORMAL] = Vec4(nx, ny, nz, 1.0f);
+       vattr[MESH_ATTR_NORMAL].data_valid = true;
+       vattr[MESH_ATTR_NORMAL].nelem = 3;
+}
+
+void Mesh::tangent(float tx, float ty, float tz)
+{
+       cur_val[MESH_ATTR_TANGENT] = Vec4(tx, ty, tz, 1.0f);
+       vattr[MESH_ATTR_TANGENT].data_valid = true;
+       vattr[MESH_ATTR_TANGENT].nelem = 3;
+}
+
+void Mesh::texcoord(float u, float v, float w)
+{
+       cur_val[MESH_ATTR_TEXCOORD] = Vec4(u, v, w, 1.0f);
+       vattr[MESH_ATTR_TEXCOORD].data_valid = true;
+       vattr[MESH_ATTR_TEXCOORD].nelem = 3;
+}
+
+void Mesh::boneweights(float w1, float w2, float w3, float w4)
+{
+       cur_val[MESH_ATTR_BONEWEIGHTS] = Vec4(w1, w2, w3, w4);
+       vattr[MESH_ATTR_BONEWEIGHTS].data_valid = true;
+       vattr[MESH_ATTR_BONEWEIGHTS].nelem = 4;
+}
+
+void Mesh::boneidx(int idx1, int idx2, int idx3, int idx4)
+{
+       cur_val[MESH_ATTR_BONEIDX] = Vec4(idx1, idx2, idx3, idx4);
+       vattr[MESH_ATTR_BONEIDX].data_valid = true;
+       vattr[MESH_ATTR_BONEIDX].nelem = 4;
+}
+
+int Mesh::get_poly_count() const
+{
+       if(nfaces) {
+               return nfaces;
+       }
+       if(nverts) {
+               return nverts / 3;
+       }
+       return 0;
+}
+
+/// static function
+void Mesh::set_attrib_location(int attr, int loc)
+{
+       if(attr < 0 || attr >= NUM_MESH_ATTR) {
+               return;
+       }
+       Mesh::global_sdr_loc[attr] = loc;
+}
+
+/// static function
+int Mesh::get_attrib_location(int attr)
+{
+       if(attr < 0 || attr >= NUM_MESH_ATTR) {
+               return -1;
+       }
+       return Mesh::global_sdr_loc[attr];
+}
+
+/// static function
+void Mesh::clear_attrib_locations()
+{
+       for(int i=0; i<NUM_MESH_ATTR; i++) {
+               Mesh::global_sdr_loc[i] = -1;
+       }
+}
+
+/// static function
+void Mesh::set_vis_vecsize(float sz)
+{
+       Mesh::vis_vecsize = sz;
+}
+
+float Mesh::get_vis_vecsize()
+{
+       return Mesh::vis_vecsize;
+}
+
+void Mesh::apply_xform(const Mat4 &xform)
+{
+       Mat4 dir_xform = xform.upper3x3();
+       apply_xform(xform, dir_xform);
+}
+
+void Mesh::apply_xform(const Mat4 &xform, const Mat4 &dir_xform)
+{
+       for(unsigned int i=0; i<nverts; i++) {
+               Vec4 v = get_attrib(MESH_ATTR_VERTEX, i);
+               set_attrib(MESH_ATTR_VERTEX, i, xform * v);
+
+               if(has_attrib(MESH_ATTR_NORMAL)) {
+                       Vec3 n = Vec3(get_attrib(MESH_ATTR_NORMAL, i));
+                       set_attrib(MESH_ATTR_NORMAL, i, Vec4(dir_xform * n));
+               }
+               if(has_attrib(MESH_ATTR_TANGENT)) {
+                       Vec3 t = Vec3(get_attrib(MESH_ATTR_TANGENT, i));
+                       set_attrib(MESH_ATTR_TANGENT, i, Vec4(dir_xform * t));
+               }
+       }
+}
+
+void Mesh::flip()
+{
+       flip_faces();
+       flip_normals();
+}
+
+void Mesh::flip_faces()
+{
+       if(is_indexed()) {
+               unsigned int *indices = get_index_data();
+               if(!indices) return;
+
+               int idxnum = get_index_count();
+               for(int i=0; i<idxnum; i+=3) {
+                       unsigned int tmp = indices[i + 2];
+                       indices[i + 2] = indices[i + 1];
+                       indices[i + 1] = tmp;
+               }
+
+       } else {
+               Vec3 *verts = (Vec3*)get_attrib_data(MESH_ATTR_VERTEX);
+               if(!verts) return;
+
+               int vnum = get_attrib_count(MESH_ATTR_VERTEX);
+               for(int i=0; i<vnum; i+=3) {
+                       Vec3 tmp = verts[i + 2];
+                       verts[i + 2] = verts[i + 1];
+                       verts[i + 1] = tmp;
+               }
+       }
+}
+
+void Mesh::flip_normals()
+{
+       Vec3 *normals = (Vec3*)get_attrib_data(MESH_ATTR_NORMAL);
+       if(!normals) return;
+
+       int num = get_attrib_count(MESH_ATTR_NORMAL);
+       for(int i=0; i<num; i++) {
+               normals[i] = -normals[i];
+       }
+}
+
+void Mesh::explode()
+{
+       if(!is_indexed()) return;       // no vertex sharing is possible in unindexed meshes
+
+       unsigned int *indices = get_index_data();
+       assert(indices);
+
+       int idxnum = get_index_count();
+       int nnverts = idxnum;
+
+       nverts = nnverts;
+
+       for(int i=0; i<NUM_MESH_ATTR; i++) {
+               if(!has_attrib(i)) continue;
+
+               const float *srcbuf = get_attrib_data(i);
+
+               float *tmpbuf = new float[nnverts * vattr[i].nelem];
+               float *dstptr = tmpbuf;
+               for(int j=0; j<idxnum; j++) {
+                       unsigned int idx = indices[j];
+                       const float *srcptr = srcbuf + idx * vattr[i].nelem;
+
+                       for(int k=0; k<vattr[i].nelem; k++) {
+                               *dstptr++ = *srcptr++;
+                       }
+               }
+               set_attrib_data(i, vattr[i].nelem, nnverts, tmpbuf);
+               delete [] tmpbuf;
+       }
+
+       ibo_valid = false;
+       idata_valid = false;
+       idata.clear();
+
+       nfaces = idxnum / 3;
+}
+
+void Mesh::calc_face_normals()
+{
+       const Vec3 *varr = (Vec3*)get_attrib_data(MESH_ATTR_VERTEX);
+       Vec3 *narr = (Vec3*)get_attrib_data(MESH_ATTR_NORMAL);
+       if(!varr) {
+               return;
+       }
+
+       if(is_indexed()) {
+               const unsigned int *idxarr = get_index_data();
+
+               for(unsigned int i=0; i<nfaces; i++) {
+                       Triangle face(i, varr, idxarr);
+                       face.calc_normal();
+
+                       for(int j=0; j<3; j++) {
+                               unsigned int idx = *idxarr++;
+                               narr[idx] = face.normal;
+                       }
+               }
+       } else {
+               // non-indexed
+               int nfaces = nverts / 3;
+
+               for(int i=0; i<nfaces; i++) {
+                       Triangle face(varr[0], varr[1], varr[2]);
+                       face.calc_normal();
+
+                       narr[0] = narr[1] = narr[2] = face.normal;
+                       varr += vattr[MESH_ATTR_VERTEX].nelem;
+                       narr += vattr[MESH_ATTR_NORMAL].nelem;
+               }
+       }
+}
+
+/*
+int Mesh::add_bone(XFormNode *bone)
+{
+       int idx = bones.size();
+       bones.push_back(bone);
+       return idx;
+}
+
+const XFormNode *Mesh::get_bone(int idx) const
+{
+       if(idx < 0 || idx >= (int)bones.size()) {
+               return 0;
+       }
+       return bones[idx];
+}
+
+int Mesh::get_bones_count() const
+{
+       return (int)bones.size();
+}
+*/
+
+bool Mesh::pre_draw() const
+{
+       cur_sdr = 0;
+       glGetIntegerv(GL_CURRENT_PROGRAM, &cur_sdr);
+
+       ((Mesh*)this)->update_buffers();
+
+       if(!vattr[MESH_ATTR_VERTEX].vbo_valid) {
+               error_log("%s: invalid vertex buffer\n", __FUNCTION__);
+               return false;
+       }
+
+       if(cur_sdr && use_custom_sdr_attr) {
+               // rendering with shaders
+               if(global_sdr_loc[MESH_ATTR_VERTEX] == -1) {
+                       error_log("%s: shader attribute location for vertices unset\n", __FUNCTION__);
+                       return false;
+               }
+
+               for(int i=0; i<NUM_MESH_ATTR; i++) {
+                       int loc = global_sdr_loc[i];
+                       if(loc >= 0 && vattr[i].vbo_valid) {
+                               glBindBuffer(GL_ARRAY_BUFFER, vattr[i].vbo);
+                               glVertexAttribPointer(loc, vattr[i].nelem, GL_FLOAT, GL_FALSE, 0, 0);
+                               glEnableVertexAttribArray(loc);
+                       }
+               }
+       } else {
+#ifndef GL_ES_VERSION_2_0
+               // rendering with fixed-function (not available in GLES2)
+               glBindBuffer(GL_ARRAY_BUFFER, vattr[MESH_ATTR_VERTEX].vbo);
+               glVertexPointer(vattr[MESH_ATTR_VERTEX].nelem, GL_FLOAT, 0, 0);
+               glEnableClientState(GL_VERTEX_ARRAY);
+
+               if(vattr[MESH_ATTR_NORMAL].vbo_valid) {
+                       glBindBuffer(GL_ARRAY_BUFFER, vattr[MESH_ATTR_NORMAL].vbo);
+                       glNormalPointer(GL_FLOAT, 0, 0);
+                       glEnableClientState(GL_NORMAL_ARRAY);
+               }
+               if(vattr[MESH_ATTR_TEXCOORD].vbo_valid) {
+                       glBindBuffer(GL_ARRAY_BUFFER, vattr[MESH_ATTR_TEXCOORD].vbo);
+                       glTexCoordPointer(vattr[MESH_ATTR_TEXCOORD].nelem, GL_FLOAT, 0, 0);
+                       glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+               }
+               if(vattr[MESH_ATTR_COLOR].vbo_valid) {
+                       glBindBuffer(GL_ARRAY_BUFFER, vattr[MESH_ATTR_COLOR].vbo);
+                       glColorPointer(vattr[MESH_ATTR_COLOR].nelem, GL_FLOAT, 0, 0);
+                       glEnableClientState(GL_COLOR_ARRAY);
+               }
+               if(vattr[MESH_ATTR_TEXCOORD2].vbo_valid) {
+                       glClientActiveTexture(GL_TEXTURE1);
+                       glBindBuffer(GL_ARRAY_BUFFER, vattr[MESH_ATTR_TEXCOORD2].vbo);
+                       glTexCoordPointer(vattr[MESH_ATTR_TEXCOORD2].nelem, GL_FLOAT, 0, 0);
+                       glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+                       glClientActiveTexture(GL_TEXTURE0);
+               }
+#endif
+       }
+       glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+       return true;
+}
+
+void Mesh::draw() const
+{
+       if(!pre_draw()) return;
+
+       if(ibo_valid) {
+               glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
+               glDrawElements(GL_TRIANGLES, nfaces * 3, GL_UNSIGNED_INT, 0);
+               glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+       } else {
+               glDrawArrays(GL_TRIANGLES, 0, nverts);
+       }
+
+       post_draw();
+}
+
+void Mesh::post_draw() const
+{
+       if(cur_sdr && use_custom_sdr_attr) {
+               // rendered with shaders
+               for(int i=0; i<NUM_MESH_ATTR; i++) {
+                       int loc = global_sdr_loc[i];
+                       if(loc >= 0 && vattr[i].vbo_valid) {
+                               glDisableVertexAttribArray(loc);
+                       }
+               }
+       } else {
+#ifndef GL_ES_VERSION_2_0
+               // rendered with fixed-function
+               glDisableClientState(GL_VERTEX_ARRAY);
+               if(vattr[MESH_ATTR_NORMAL].vbo_valid) {
+                       glDisableClientState(GL_NORMAL_ARRAY);
+               }
+               if(vattr[MESH_ATTR_TEXCOORD].vbo_valid) {
+                       glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+               }
+               if(vattr[MESH_ATTR_COLOR].vbo_valid) {
+                       glDisableClientState(GL_COLOR_ARRAY);
+               }
+               if(vattr[MESH_ATTR_TEXCOORD2].vbo_valid) {
+                       glClientActiveTexture(GL_TEXTURE1);
+                       glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+                       glClientActiveTexture(GL_TEXTURE0);
+               }
+#endif
+       }
+}
+
+void Mesh::draw_wire() const
+{
+       if(!pre_draw()) return;
+
+       ((Mesh*)this)->update_wire_ibo();
+
+       int num_faces = get_poly_count();
+       glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, wire_ibo);
+       glDrawElements(GL_LINES, num_faces * 6, GL_UNSIGNED_INT, 0);
+       glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+       post_draw();
+}
+
+void Mesh::draw_vertices() const
+{
+       if(!pre_draw()) return;
+
+       glDrawArrays(GL_POINTS, 0, nverts);
+
+       post_draw();
+}
+
+void Mesh::draw_normals() const
+{
+#ifdef USE_OLDGL
+       int cur_sdr = 0;
+       glGetIntegerv(GL_CURRENT_PROGRAM, &cur_sdr);
+
+       Vec3 *varr = (Vec3*)get_attrib_data(MESH_ATTR_VERTEX);
+       Vec3 *norm = (Vec3*)get_attrib_data(MESH_ATTR_NORMAL);
+       if(!varr || !norm) {
+               return;
+       }
+
+       glBegin(GL_LINES);
+       if(cur_sdr && use_custom_sdr_attr) {
+               int vert_loc = global_sdr_loc[MESH_ATTR_VERTEX];
+               if(vert_loc < 0) {
+                       glEnd();
+                       return;
+               }
+
+               for(size_t i=0; i<nverts; i++) {
+                       glVertexAttrib3f(vert_loc, varr[i].x, varr[i].y, varr[i].z);
+                       Vec3 end = varr[i] + norm[i] * vis_vecsize;
+                       glVertexAttrib3f(vert_loc, end.x, end.y, end.z);
+               }
+       } else {
+               for(size_t i=0; i<nverts; i++) {
+                       glVertex3f(varr[i].x, varr[i].y, varr[i].z);
+                       Vec3 end = varr[i] + norm[i] * vis_vecsize;
+                       glVertex3f(end.x, end.y, end.z);
+               }
+       }
+       glEnd();
+#endif // USE_OLDGL
+}
+
+void Mesh::draw_tangents() const
+{
+#ifdef USE_OLDGL
+       int cur_sdr = 0;
+       glGetIntegerv(GL_CURRENT_PROGRAM, &cur_sdr);
+
+       Vec3 *varr = (Vec3*)get_attrib_data(MESH_ATTR_VERTEX);
+       Vec3 *tang = (Vec3*)get_attrib_data(MESH_ATTR_TANGENT);
+       if(!varr || !tang) {
+               return;
+       }
+
+       glBegin(GL_LINES);
+       if(cur_sdr && use_custom_sdr_attr) {
+               int vert_loc = global_sdr_loc[MESH_ATTR_VERTEX];
+               if(vert_loc < 0) {
+                       glEnd();
+                       return;
+               }
+
+               for(size_t i=0; i<nverts; i++) {
+                       glVertexAttrib3f(vert_loc, varr[i].x, varr[i].y, varr[i].z);
+                       Vec3 end = varr[i] + tang[i] * vis_vecsize;
+                       glVertexAttrib3f(vert_loc, end.x, end.y, end.z);
+               }
+       } else {
+               for(size_t i=0; i<nverts; i++) {
+                       glVertex3f(varr[i].x, varr[i].y, varr[i].z);
+                       Vec3 end = varr[i] + tang[i] * vis_vecsize;
+                       glVertex3f(end.x, end.y, end.z);
+               }
+       }
+       glEnd();
+#endif // USE_OLDGL
+}
+
+void Mesh::get_aabbox(Vec3 *vmin, Vec3 *vmax) const
+{
+       if(!aabb_valid) {
+               ((Mesh*)this)->calc_aabb();
+       }
+       *vmin = aabb.min;
+       *vmax = aabb.max;
+}
+
+const AABox &Mesh::get_aabbox() const
+{
+       if(!aabb_valid) {
+               ((Mesh*)this)->calc_aabb();
+       }
+       return aabb;
+}
+
+float Mesh::get_bsphere(Vec3 *center, float *rad) const
+{
+       if(!bsph_valid) {
+               ((Mesh*)this)->calc_bsph();
+       }
+       *center = bsph.center;
+       *rad = bsph.radius;
+       return bsph.radius;
+}
+
+const Sphere &Mesh::get_bsphere() const
+{
+       if(!bsph_valid) {
+               ((Mesh*)this)->calc_bsph();
+       }
+       return bsph;
+}
+
+/// static function
+void Mesh::set_intersect_mode(unsigned int mode)
+{
+       Mesh::intersect_mode = mode;
+}
+
+/// static function
+unsigned int Mesh::get_intersect_mode()
+{
+       return Mesh::intersect_mode;
+}
+
+/// static function
+void Mesh::set_vertex_select_distance(float dist)
+{
+       Mesh::vertex_sel_dist = dist;
+}
+
+/// static function
+float Mesh::get_vertex_select_distance()
+{
+       return Mesh::vertex_sel_dist;
+}
+
+bool Mesh::intersect(const Ray &ray, HitPoint *hit) const
+{
+       assert((Mesh::intersect_mode & (ISECT_VERTICES | ISECT_FACE)) != (ISECT_VERTICES | ISECT_FACE));
+
+       const Vec3 *varr = (Vec3*)get_attrib_data(MESH_ATTR_VERTEX);
+       const Vec3 *narr = (Vec3*)get_attrib_data(MESH_ATTR_NORMAL);
+       if(!varr) {
+               return false;
+       }
+       const unsigned int *idxarr = get_index_data();
+
+       // first test with the bounding box
+       AABox box;
+       get_aabbox(&box.min, &box.max);
+       if(!box.intersect(ray)) {
+               return false;
+       }
+
+       HitPoint nearest_hit;
+       nearest_hit.dist = FLT_MAX;
+       nearest_hit.data = 0;
+
+       if(Mesh::intersect_mode & ISECT_VERTICES) {
+               // we asked for "intersections" with the vertices of the mesh
+               long nearest_vidx = -1;
+               float thres_sq = Mesh::vertex_sel_dist * Mesh::vertex_sel_dist;
+
+               for(unsigned int i=0; i<nverts; i++) {
+
+                       if((Mesh::intersect_mode & ISECT_FRONT) && dot(narr[i], ray.dir) > 0) {
+                               continue;
+                       }
+
+                       // project the vertex onto the ray line
+                       float t = dot(varr[i] - ray.origin, ray.dir);
+                       Vec3 vproj = ray.origin + ray.dir * t;
+
+                       float dist_sq = length_sq(vproj - varr[i]);
+                       if(dist_sq < thres_sq) {
+                               if(!hit) {
+                                       return true;
+                               }
+                               if(t < nearest_hit.dist) {
+                                       nearest_hit.dist = t;
+                                       nearest_vidx = i;
+                               }
+                       }
+               }
+
+               if(nearest_vidx != -1) {
+                       hitvert = varr[nearest_vidx];
+                       nearest_hit.data = &hitvert;
+               }
+
+       } else {
+               // regular intersection test with polygons
+
+               for(unsigned int i=0; i<nfaces; i++) {
+                       Triangle face(i, varr, idxarr);
+
+                       // ignore back-facing polygons if the mode flags include ISECT_FRONT
+                       if((Mesh::intersect_mode & ISECT_FRONT) && dot(face.get_normal(), ray.dir) > 0) {
+                               continue;
+                       }
+
+                       HitPoint fhit;
+                       if(face.intersect(ray, hit ? &fhit : 0)) {
+                               if(!hit) {
+                                       return true;
+                               }
+                               if(fhit.dist < nearest_hit.dist) {
+                                       nearest_hit = fhit;
+                                       hitface = face;
+                               }
+                       }
+               }
+       }
+
+       if(nearest_hit.data) {
+               if(hit) {
+                       *hit = nearest_hit;
+
+                       // if we are interested in the mesh and not the faces set obj to this
+                       if(Mesh::intersect_mode & ISECT_FACE) {
+                               hit->data = &hitface;
+                       } else if(Mesh::intersect_mode & ISECT_VERTICES) {
+                               hit->data = &hitvert;
+                       } else {
+                               hit->data = (void*)this;
+                       }
+               }
+               return true;
+       }
+       return false;
+}
+
+
+// texture coordinate manipulation
+void Mesh::texcoord_apply_xform(const Mat4 &xform)
+{
+       if(!has_attrib(MESH_ATTR_TEXCOORD)) {
+               return;
+       }
+
+       for(unsigned int i=0; i<nverts; i++) {
+               Vec4 tc = get_attrib(MESH_ATTR_TEXCOORD, i);
+               set_attrib(MESH_ATTR_TEXCOORD, i, xform * tc);
+       }
+}
+
+void Mesh::texcoord_gen_plane(const Vec3 &norm, const Vec3 &tang)
+{
+       if(!nverts) return;
+
+       if(!has_attrib(MESH_ATTR_TEXCOORD)) {
+               // allocate texture coordinate attribute array
+               set_attrib_data(MESH_ATTR_TEXCOORD, 2, nverts);
+       }
+
+       Vec3 n = normalize(norm);
+       Vec3 b = normalize(cross(n, tang));
+       Vec3 t = cross(b, n);
+
+       for(unsigned int i=0; i<nverts; i++) {
+               Vec3 pos = Vec3(get_attrib(MESH_ATTR_VERTEX, i));
+
+               // distance along the tangent direction
+               float u = dot(pos, t);
+               // distance along the bitangent direction
+               float v = dot(pos, b);
+
+               set_attrib(MESH_ATTR_TEXCOORD, i, Vec4(u, v, 0, 1));
+       }
+}
+
+void Mesh::texcoord_gen_box()
+{
+       if(!nverts || !has_attrib(MESH_ATTR_NORMAL)) return;
+
+       if(!has_attrib(MESH_ATTR_TEXCOORD)) {
+               // allocate texture coordinate attribute array
+               set_attrib_data(MESH_ATTR_TEXCOORD, 2, nverts);
+       }
+
+       for(unsigned int i=0; i<nverts; i++) {
+               Vec3 pos = Vec3(get_attrib(MESH_ATTR_VERTEX, i)) * 0.5 + Vec3(0.5, 0.5, 0.5);
+               Vec3 norm = Vec3(get_attrib(MESH_ATTR_NORMAL, i));
+
+               float abs_nx = fabs(norm.x);
+               float abs_ny = fabs(norm.y);
+               float abs_nz = fabs(norm.z);
+               int dom = abs_nx > abs_ny && abs_nx > abs_nz ? 0 : (abs_ny > abs_nz ? 1 : 2);
+
+               float uv[2], *uvptr = uv;
+               for(int j=0; j<3; j++) {
+                       if(j == dom) continue;  // skip dominant axis
+
+                       *uvptr++ = pos[j];
+               }
+               set_attrib(MESH_ATTR_TEXCOORD, i, Vec4(uv[0], uv[1], 0, 1));
+       }
+}
+
+void Mesh::texcoord_gen_cylinder()
+{
+       if(!nverts) return;
+
+       if(!has_attrib(MESH_ATTR_TEXCOORD)) {
+               // allocate texture coordinate attribute array
+               set_attrib_data(MESH_ATTR_TEXCOORD, 2, nverts);
+       }
+
+       for(unsigned int i=0; i<nverts; i++) {
+               Vec3 pos = Vec3(get_attrib(MESH_ATTR_VERTEX, i));
+
+               float rho = sqrt(pos.x * pos.x + pos.z * pos.z);
+               float theta = rho == 0.0 ? 0.0 : atan2(pos.z / rho, pos.x / rho);
+
+               float u = theta / (2.0 * M_PI) + 0.5;
+               float v = pos.y;
+
+               set_attrib(MESH_ATTR_TEXCOORD, i, Vec4(u, v, 0, 1));
+       }
+}
+
+
+bool Mesh::dump(const char *fname) const
+{
+       FILE *fp = fopen(fname, "wb");
+       if(fp) {
+               bool res = dump(fp);
+               fclose(fp);
+               return res;
+       }
+       return false;
+}
+
+bool Mesh::dump(FILE *fp) const
+{
+       if(!has_attrib(MESH_ATTR_VERTEX)) {
+               return false;
+       }
+
+       fprintf(fp, "VERTEX ATTRIBUTES\n");
+       static const char *label[] = { "pos", "nor", "tan", "tex", "col", "bw", "bid", "tex2" };
+       static const char *elemfmt[] = { 0, " %s(%g)", " %s(%g, %g)", " %s(%g, %g, %g)", " %s(%g, %g, %g, %g)", 0 };
+
+       for(int i=0; i<(int)nverts; i++) {
+               fprintf(fp, "%5u:", i);
+               for(int j=0; j<NUM_MESH_ATTR; j++) {
+                       if(has_attrib(j)) {
+                               Vec4 v = get_attrib(j, i);
+                               int nelem = vattr[j].nelem;
+                               fprintf(fp, elemfmt[nelem], label[j], v.x, v.y, v.z, v.w);
+                       }
+               }
+               fputc('\n', fp);
+       }
+
+       if(is_indexed()) {
+               const unsigned int *idx = get_index_data();
+               int numidx = get_index_count();
+               int numtri = numidx / 3;
+               assert(numidx % 3 == 0);
+
+               fprintf(fp, "FACES\n");
+
+               for(int i=0; i<numtri; i++) {
+                       fprintf(fp, "%5d: %d %d %d\n", i, idx[0], idx[1], idx[2]);
+                       idx += 3;
+               }
+       }
+       return true;
+}
+
+bool Mesh::dump_obj(const char *fname) const
+{
+       FILE *fp = fopen(fname, "wb");
+       if(fp) {
+               bool res = dump_obj(fp);
+               fclose(fp);
+               return res;
+       }
+       return false;
+}
+
+bool Mesh::dump_obj(FILE *fp) const
+{
+       if(!has_attrib(MESH_ATTR_VERTEX)) {
+               return false;
+       }
+
+       for(int i=0; i<(int)nverts; i++) {
+               Vec4 v = get_attrib(MESH_ATTR_VERTEX, i);
+               fprintf(fp, "v %g %g %g\n", v.x, v.y, v.z);
+       }
+
+       if(has_attrib(MESH_ATTR_NORMAL)) {
+               for(int i=0; i<(int)nverts; i++) {
+                       Vec4 v = get_attrib(MESH_ATTR_NORMAL, i);
+                       fprintf(fp, "vn %g %g %g\n", v.x, v.y, v.z);
+               }
+       }
+
+       if(has_attrib(MESH_ATTR_TEXCOORD)) {
+               for(int i=0; i<(int)nverts; i++) {
+                       Vec4 v = get_attrib(MESH_ATTR_TEXCOORD, i);
+                       fprintf(fp, "vt %g %g\n", v.x, v.y);
+               }
+       }
+
+       if(is_indexed()) {
+               const unsigned int *idxptr = get_index_data();
+               int numidx = get_index_count();
+               int numtri = numidx / 3;
+               assert(numidx % 3 == 0);
+
+               for(int i=0; i<numtri; i++) {
+                       fputc('f', fp);
+                       for(int j=0; j<3; j++) {
+                               unsigned int idx = *idxptr++ + 1;
+                               fprintf(fp, " %u/%u/%u", idx, idx, idx);
+                       }
+                       fputc('\n', fp);
+               }
+       } else {
+               int numtri = nverts / 3;
+               unsigned int idx = 1;
+               for(int i=0; i<numtri; i++) {
+                       fputc('f', fp);
+                       for(int j=0; j<3; j++) {
+                               fprintf(fp, " %u/%u/%u", idx, idx, idx);
+                               ++idx;
+                       }
+                       fputc('\n', fp);
+               }
+       }
+       return true;
+}
+
+// ------ private member functions ------
+
+void Mesh::calc_aabb()
+{
+       // the cast is to force calling the const version which doesn't invalidate
+       if(!((const Mesh*)this)->get_attrib_data(MESH_ATTR_VERTEX)) {
+               return;
+       }
+
+       aabb.min = Vec3(FLT_MAX, FLT_MAX, FLT_MAX);
+       aabb.max = -aabb.min;
+
+       for(unsigned int i=0; i<nverts; i++) {
+               Vec4 v = get_attrib(MESH_ATTR_VERTEX, i);
+               for(int j=0; j<3; j++) {
+                       if(v[j] < aabb.min[j]) {
+                               aabb.min[j] = v[j];
+                       }
+                       if(v[j] > aabb.max[j]) {
+                               aabb.max[j] = v[j];
+                       }
+               }
+       }
+       aabb_valid = true;
+}
+
+void Mesh::calc_bsph()
+{
+       // the cast is to force calling the const version which doesn't invalidate
+       if(!((const Mesh*)this)->get_attrib_data(MESH_ATTR_VERTEX)) {
+               return;
+       }
+
+       Vec3 v;
+       bsph.center = Vec3(0, 0, 0);
+
+       // first find the center
+       for(unsigned int i=0; i<nverts; i++) {
+               v = Vec3(get_attrib(MESH_ATTR_VERTEX, i));
+               bsph.center += v;
+       }
+       bsph.center /= (float)nverts;
+
+       bsph.radius = 0.0f;
+       for(unsigned int i=0; i<nverts; i++) {
+               v = Vec3(get_attrib(MESH_ATTR_VERTEX, i));
+               float dist_sq = length_sq(v - bsph.center);
+               if(dist_sq > bsph.radius) {
+                       bsph.radius = dist_sq;
+               }
+       }
+       bsph.radius = sqrt(bsph.radius);
+
+       bsph_valid = true;
+}
+
+void Mesh::update_buffers()
+{
+       for(int i=0; i<NUM_MESH_ATTR; i++) {
+               if(has_attrib(i) && !vattr[i].vbo_valid) {
+                       glBindBuffer(GL_ARRAY_BUFFER, vattr[i].vbo);
+                       glBufferData(GL_ARRAY_BUFFER, nverts * vattr[i].nelem * sizeof(float), &vattr[i].data[0], GL_STATIC_DRAW);
+                       vattr[i].vbo_valid = true;
+               }
+       }
+       glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+       if(idata_valid && !ibo_valid) {
+               glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
+               glBufferData(GL_ELEMENT_ARRAY_BUFFER, nfaces * 3 * sizeof(unsigned int), &idata[0], GL_STATIC_DRAW);
+               ibo_valid = true;
+       }
+       glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+}
+
+void Mesh::update_wire_ibo()
+{
+       update_buffers();
+
+       if(wire_ibo_valid) {
+               return;
+       }
+
+       if(!wire_ibo) {
+               glGenBuffers(1, &wire_ibo);
+       }
+
+       int num_faces = get_poly_count();
+
+       unsigned int *wire_idxarr = new unsigned int[num_faces * 6];
+       unsigned int *dest = wire_idxarr;
+
+       if(ibo_valid) {
+               // we're dealing with an indexed mesh
+               const unsigned int *idxarr = ((const Mesh*)this)->get_index_data();
+
+               for(int i=0; i<num_faces; i++) {
+                       *dest++ = idxarr[0];
+                       *dest++ = idxarr[1];
+                       *dest++ = idxarr[1];
+                       *dest++ = idxarr[2];
+                       *dest++ = idxarr[2];
+                       *dest++ = idxarr[0];
+                       idxarr += 3;
+               }
+       } else {
+               // not an indexed mesh ...
+               for(int i=0; i<num_faces; i++) {
+                       int vidx = i * 3;
+                       *dest++ = vidx;
+                       *dest++ = vidx + 1;
+                       *dest++ = vidx + 1;
+                       *dest++ = vidx + 2;
+                       *dest++ = vidx + 2;
+                       *dest++ = vidx;
+               }
+       }
+
+       glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, wire_ibo);
+       glBufferData(GL_ELEMENT_ARRAY_BUFFER, num_faces * 6 * sizeof(unsigned int), wire_idxarr, GL_STATIC_DRAW);
+       delete [] wire_idxarr;
+       wire_ibo_valid = true;
+       glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+}
+
+
+// ------ class Triangle ------
+Triangle::Triangle()
+{
+       normal_valid = false;
+       id = -1;
+}
+
+Triangle::Triangle(const Vec3 &v0, const Vec3 &v1, const Vec3 &v2)
+{
+       v[0] = v0;
+       v[1] = v1;
+       v[2] = v2;
+       normal_valid = false;
+       id = -1;
+}
+
+Triangle::Triangle(int n, const Vec3 *varr, const unsigned int *idxarr)
+{
+       if(idxarr) {
+               v[0] = varr[idxarr[n * 3]];
+               v[1] = varr[idxarr[n * 3 + 1]];
+               v[2] = varr[idxarr[n * 3 + 2]];
+       } else {
+               v[0] = varr[n * 3];
+               v[1] = varr[n * 3 + 1];
+               v[2] = varr[n * 3 + 2];
+       }
+       normal_valid = false;
+       id = n;
+}
+
+void Triangle::calc_normal()
+{
+       normal = normalize(cross(v[1] - v[0], v[2] - v[0]));
+       normal_valid = true;
+}
+
+const Vec3 &Triangle::get_normal() const
+{
+       if(!normal_valid) {
+               ((Triangle*)this)->calc_normal();
+       }
+       return normal;
+}
+
+void Triangle::transform(const Mat4 &xform)
+{
+       v[0] = xform * v[0];
+       v[1] = xform * v[1];
+       v[2] = xform * v[2];
+       normal_valid = false;
+}
+
+void Triangle::draw() const
+{
+       Vec3 n[3];
+       n[0] = n[1] = n[2] = get_normal();
+
+       int vloc = Mesh::get_attrib_location(MESH_ATTR_VERTEX);
+       int nloc = Mesh::get_attrib_location(MESH_ATTR_NORMAL);
+
+       glEnableVertexAttribArray(vloc);
+       glVertexAttribPointer(vloc, 3, GL_FLOAT, GL_FALSE, 0, &v[0].x);
+       glVertexAttribPointer(nloc, 3, GL_FLOAT, GL_FALSE, 0, &n[0].x);
+
+       glDrawArrays(GL_TRIANGLES, 0, 3);
+
+       glDisableVertexAttribArray(vloc);
+       glDisableVertexAttribArray(nloc);
+}
+
+void Triangle::draw_wire() const
+{
+       static const int idxarr[] = {0, 1, 1, 2, 2, 0};
+       int vloc = Mesh::get_attrib_location(MESH_ATTR_VERTEX);
+
+       glEnableVertexAttribArray(vloc);
+       glVertexAttribPointer(vloc, 3, GL_FLOAT, GL_FALSE, 0, &v[0].x);
+
+       glDrawElements(GL_LINES, 6, GL_UNSIGNED_INT, idxarr);
+
+       glDisableVertexAttribArray(vloc);
+}
+
+Vec3 Triangle::calc_barycentric(const Vec3 &pos) const
+{
+       Vec3 norm = get_normal();
+
+       float area_sq = fabs(dot(cross(v[1] - v[0], v[2] - v[0]), norm));
+       if(area_sq < 1e-5) {
+               return Vec3(0, 0, 0);
+       }
+
+       float asq0 = fabs(dot(cross(v[1] - pos, v[2] - pos), norm));
+       float asq1 = fabs(dot(cross(v[2] - pos, v[0] - pos), norm));
+       float asq2 = fabs(dot(cross(v[0] - pos, v[1] - pos), norm));
+
+       return Vec3(asq0 / area_sq, asq1 / area_sq, asq2 / area_sq);
+}
+
+bool Triangle::intersect(const Ray &ray, HitPoint *hit) const
+{
+       Vec3 normal = get_normal();
+
+       float ndotdir = dot(ray.dir, normal);
+       if(fabs(ndotdir) < 1e-4) {
+               return false;
+       }
+
+       Vec3 vertdir = v[0] - ray.origin;
+       float t = dot(normal, vertdir) / ndotdir;
+
+       Vec3 pos = ray.origin + ray.dir * t;
+       Vec3 bary = calc_barycentric(pos);
+
+       if(bary.x + bary.y + bary.z > 1.00001) {
+               return false;
+       }
+
+       if(hit) {
+               hit->dist = t;
+               hit->pos = ray.origin + ray.dir * t;
+               hit->normal = normal;
+               hit->data = (void*)this;
+       }
+       return true;
+}
diff --git a/src/mesh.h b/src/mesh.h
new file mode 100644 (file)
index 0000000..90cd1e7
--- /dev/null
@@ -0,0 +1,248 @@
+#ifndef MESH_H_
+#define MESH_H_
+
+#include <stdio.h>
+#include <string>
+#include <vector>
+#include "gmath/gmath.h"
+#include "geom.h"
+
+enum {
+       MESH_ATTR_VERTEX,
+       MESH_ATTR_NORMAL,
+       MESH_ATTR_TANGENT,
+       MESH_ATTR_TEXCOORD,
+       MESH_ATTR_COLOR,
+       MESH_ATTR_BONEWEIGHTS,
+       MESH_ATTR_BONEIDX,
+       MESH_ATTR_TEXCOORD2,
+
+       NUM_MESH_ATTR
+};
+
+// intersection mode flags
+enum {
+       ISECT_DEFAULT   = 0,    // default (whole mesh, all intersections)
+       ISECT_FRONT             = 1,    // front-faces only
+       ISECT_FACE              = 2,    // return intersected face pointer instead of mesh
+       ISECT_VERTICES  = 4             // return (?) TODO
+};
+
+//class XFormNode;
+
+
+class Triangle {
+public:
+       Vec3 v[3];
+       Vec3 normal;
+       bool normal_valid;
+       int id;
+
+       Triangle();
+       Triangle(const Vec3 &v0, const Vec3 &v1, const Vec3 &v2);
+       Triangle(int n, const Vec3 *varr, const unsigned int *idxarr = 0);
+
+       /// calculate normal (quite expensive)
+       void calc_normal();
+       const Vec3 &get_normal() const;
+
+       void transform(const Mat4 &xform);
+
+       void draw() const;
+       void draw_wire() const;
+
+       /// calculate barycentric coordinates of a point
+       Vec3 calc_barycentric(const Vec3 &pos) const;
+
+       bool intersect(const Ray &ray, HitPoint *hit = 0) const;
+};
+
+
+class Mesh {
+private:
+       std::string name;
+       unsigned int nverts, nfaces;
+
+       // current value for each attribute for the immedate mode
+       // interface.
+       Vec4 cur_val[NUM_MESH_ATTR];
+
+       unsigned int buffer_objects[NUM_MESH_ATTR + 1];
+
+       // vertex attribute data and buffer objects
+       struct VertexAttrib {
+               int nelem;                                      // number of elements per attribute range: [1, 4]
+               std::vector<float> data;
+               unsigned int vbo;
+               mutable bool vbo_valid;         // if this is false, the vbo needs updating from the data
+               mutable bool data_valid;        // if this is false, the data needs to be pulled from the vbo
+               //int sdr_loc;
+       } vattr[NUM_MESH_ATTR];
+
+       static int global_sdr_loc[NUM_MESH_ATTR];
+
+       //std::vector<XFormNode*> bones;        // bones affecting this mesh
+
+       // index data and buffer object
+       std::vector<unsigned int> idata;
+       unsigned int ibo;
+       mutable bool ibo_valid;
+       mutable bool idata_valid;
+
+       // index buffer object for wireframe rendering (constructed on demand)
+       unsigned int wire_ibo;
+       mutable bool wire_ibo_valid;
+
+       // axis-aligned bounding box
+       mutable AABox aabb;
+       mutable bool aabb_valid;
+
+       // bounding sphere
+       mutable Sphere bsph;
+       mutable bool bsph_valid;
+
+       // keeps the last intersected face
+       mutable Triangle hitface;
+       // keeps the last intersected vertex position
+       mutable Vec3 hitvert;
+
+       void calc_aabb();
+       void calc_bsph();
+
+       static unsigned int intersect_mode;
+       static float vertex_sel_dist;
+
+       static float vis_vecsize;
+
+       /// update the VBOs after data has changed (invalid vbo/ibo)
+       void update_buffers();
+       /// construct/update the wireframe index buffer (called from draw_wire).
+       void update_wire_ibo();
+
+       mutable int cur_sdr;
+       bool pre_draw() const;
+       void post_draw() const;
+
+
+public:
+       static bool use_custom_sdr_attr;
+
+       Mesh();
+       ~Mesh();
+
+       Mesh(const Mesh &rhs);
+       Mesh &operator =(const Mesh &rhs);
+       bool clone(const Mesh &m);
+
+       void set_name(const char *name);
+       const char *get_name() const;
+
+       bool has_attrib(int attr) const;
+       bool is_indexed() const;
+
+       // clears everything about this mesh, and returns to the newly constructed state
+       void clear();
+
+       // access the vertex attribute data
+       // if vdata == 0, space is just allocated
+       float *set_attrib_data(int attrib, int nelem, unsigned int num, const float *vdata = 0); // invalidates vbo
+       float *get_attrib_data(int attrib);     // invalidates vbo
+       const float *get_attrib_data(int attrib) const;
+
+       // simple access to any particular attribute
+       void set_attrib(int attrib, int idx, const Vec4 &v); // invalidates vbo
+       Vec4 get_attrib(int attrib, int idx) const;
+
+       int get_attrib_count(int attrib) const;
+
+       // ... same for index data
+       unsigned int *set_index_data(int num, const unsigned int *indices = 0); // invalidates ibo
+       unsigned int *get_index_data(); // invalidates ibo
+       const unsigned int *get_index_data() const;
+
+       int get_index_count() const;
+
+       void append(const Mesh &mesh);
+
+       // immediate-mode style mesh construction interface
+       void vertex(float x, float y, float z);
+       void normal(float nx, float ny, float nz);
+       void tangent(float tx, float ty, float tz);
+       void texcoord(float u, float v, float w);
+       void boneweights(float w1, float w2, float w3, float w4);
+       void boneidx(int idx1, int idx2, int idx3, int idx4);
+
+       int get_poly_count() const;
+
+       /* apply a transformation to the vertices and its inverse-transpose
+        * to the normals and tangents.
+        */
+       void apply_xform(const Mat4 &xform);
+       void apply_xform(const Mat4 &xform, const Mat4 &dir_xform);
+
+       void flip();    // both faces and normals
+       void flip_faces();
+       void flip_normals();
+
+       void explode(); // undo all vertex sharing
+
+       void calc_face_normals(); // this is only guaranteed to work on an exploded mesh
+
+       // adds a bone and returns its index
+       /*int add_bone(XFormNode *bone);
+       const XFormNode *get_bone(int idx) const;
+       int get_bones_count() const;*/
+
+       // access the shader attribute locations
+       static void set_attrib_location(int attr, int loc);
+       static int get_attrib_location(int attr);
+       static void clear_attrib_locations();
+
+       static void set_vis_vecsize(float sz);
+       static float get_vis_vecsize();
+
+       void draw() const;
+       void draw_wire() const;
+       void draw_vertices() const;
+       void draw_normals() const;
+       void draw_tangents() const;
+
+       /** get the bounding box in local space. The result will be cached, and subsequent
+        * calls will return the same box. The cache gets invalidated by any functions that can affect
+        * the vertex data (non-const variant of get_attrib_data(MESH_ATTR_VERTEX, ...) included).
+        * @{ */
+       void get_aabbox(Vec3 *vmin, Vec3 *vmax) const;
+       const AABox &get_aabbox() const;
+       /// @}
+
+       /** get the bounding sphere in local space. The result will be cached, and subsequent
+        * calls will return the same box. The cache gets invalidated by any functions that can affect
+        * the vertex data (non-const variant of get_attrib_data(MESH_ATTR_VERTEX, ...) included).
+        * @{ */
+       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 void set_vertex_select_distance(float dist);
+       static float get_vertex_select_distance();
+
+       /** Find the intersection between the mesh and a ray.
+        * XXX Brute force at the moment, not intended to be used for anything other than picking in tools.
+        *     If you intend to use it in a speed-critical part of the code, you'll *have* to optimize it!
+        */
+       bool intersect(const Ray &ray, HitPoint *hit = 0) const;
+
+       // texture coordinate manipulation
+       void texcoord_apply_xform(const Mat4 &xform);
+       void texcoord_gen_plane(const Vec3 &norm, const Vec3 &tang);
+       void texcoord_gen_box();
+       void texcoord_gen_cylinder();
+
+       bool dump(const char *fname) const;
+       bool dump(FILE *fp) const;
+       bool dump_obj(const char *fname) const;
+       bool dump_obj(FILE *fp) const;
+};
+
+#endif // MESH_H_
diff --git a/src/meshgen.cc b/src/meshgen.cc
new file mode 100644 (file)
index 0000000..7664aea
--- /dev/null
@@ -0,0 +1,883 @@
+#include <stdio.h>
+#include "meshgen.h"
+#include "mesh.h"
+
+// -------- sphere --------
+
+#define SURAD(u)       ((u) * 2.0 * M_PI)
+#define SVRAD(v)       ((v) * M_PI)
+
+static Vec3 sphvec(float theta, float phi)
+{
+       return Vec3(sin(theta) * sin(phi),
+                       cos(phi),
+                       cos(theta) * sin(phi));
+}
+
+void gen_sphere(Mesh *mesh, float rad, int usub, int vsub, float urange, float vrange)
+{
+       if(urange == 0.0 || vrange == 0.0) return;
+
+       if(usub < 4) usub = 4;
+       if(vsub < 2) vsub = 2;
+
+       int uverts = usub + 1;
+       int vverts = vsub + 1;
+
+       int num_verts = uverts * vverts;
+       int num_quads = usub * vsub;
+       int num_tri = num_quads * 2;
+
+       mesh->clear();
+       Vec3 *varr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_VERTEX, 3, num_verts, 0);
+       Vec3 *narr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_NORMAL, 3, num_verts, 0);
+       Vec3 *tarr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_TANGENT, 3, num_verts, 0);
+       Vec2 *uvarr = (Vec2*)mesh->set_attrib_data(MESH_ATTR_TEXCOORD, 2, num_verts, 0);
+       unsigned int *idxarr = mesh->set_index_data(num_tri * 3, 0);
+
+       float du = urange / (float)(uverts - 1);
+       float dv = vrange / (float)(vverts - 1);
+
+       float u = 0.0;
+       for(int i=0; i<uverts; i++) {
+               float theta = u * 2.0 * M_PI;
+
+               float v = 0.0;
+               for(int j=0; j<vverts; j++) {
+                       float phi = v * M_PI;
+
+                       Vec3 pos = sphvec(theta, phi);
+
+                       *varr++ = pos * rad;
+                       *narr++ = pos;
+                       *tarr++ = normalize(sphvec(theta + 0.1f, (float)M_PI / 2.0f) - sphvec(theta - 0.1f, (float)M_PI / 2.0f));
+                       *uvarr++ = Vec2(u / urange, v / vrange);
+
+                       if(i < usub && j < vsub) {
+                               int idx = i * vverts + j;
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + 1;
+                               *idxarr++ = idx + vverts + 1;
+
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + vverts + 1;
+                               *idxarr++ = idx + vverts;
+                       }
+
+                       v += dv;
+               }
+               u += du;
+       }
+}
+
+// ------ geosphere ------
+#define PHI            1.618034
+
+static Vec3 icosa_pt[] = {
+       Vec3(PHI, 1, 0),
+       Vec3(-PHI, 1, 0),
+       Vec3(PHI, -1, 0),
+       Vec3(-PHI, -1, 0),
+       Vec3(1, 0, PHI),
+       Vec3(1, 0, -PHI),
+       Vec3(-1, 0, PHI),
+       Vec3(-1, 0, -PHI),
+       Vec3(0, PHI, 1),
+       Vec3(0, -PHI, 1),
+       Vec3(0, PHI, -1),
+       Vec3(0, -PHI, -1)
+};
+enum { P11, P12, P13, P14, P21, P22, P23, P24, P31, P32, P33, P34 };
+static int icosa_idx[] = {
+       P11, P31, P21,
+       P11, P22, P33,
+       P13, P21, P32,
+       P13, P34, P22,
+       P12, P23, P31,
+       P12, P33, P24,
+       P14, P32, P23,
+       P14, P24, P34,
+
+       P11, P33, P31,
+       P12, P31, P33,
+       P13, P32, P34,
+       P14, P34, P32,
+
+       P21, P13, P11,
+       P22, P11, P13,
+       P23, P12, P14,
+       P24, P14, P12,
+
+       P31, P23, P21,
+       P32, P21, P23,
+       P33, P22, P24,
+       P34, P24, P22
+};
+
+static void geosphere(std::vector<Vec3> *verts, const Vec3 &v1, const Vec3 &v2, const Vec3 &v3, int iter)
+{
+       if(!iter) {
+               verts->push_back(v1);
+               verts->push_back(v2);
+               verts->push_back(v3);
+               return;
+       }
+
+       Vec3 v12 = normalize(v1 + v2);
+       Vec3 v23 = normalize(v2 + v3);
+       Vec3 v31 = normalize(v3 + v1);
+
+       geosphere(verts, v1, v12, v31, iter - 1);
+       geosphere(verts, v2, v23, v12, iter - 1);
+       geosphere(verts, v3, v31, v23, iter - 1);
+       geosphere(verts, v12, v23, v31, iter - 1);
+}
+
+void gen_geosphere(Mesh *mesh, float rad, int subdiv, bool hemi)
+{
+       int num_tri = (sizeof icosa_idx / sizeof *icosa_idx) / 3;
+
+       std::vector<Vec3> verts;
+       for(int i=0; i<num_tri; i++) {
+               Vec3 v[3];
+
+               for(int j=0; j<3; j++) {
+                       int vidx = icosa_idx[i * 3 + j];
+                       v[j] = normalize(icosa_pt[vidx]);
+               }
+
+               if(hemi && (v[0].y < 0.0 || v[1].y < 0.0 || v[2].y < 0.0)) {
+                       continue;
+               }
+
+               geosphere(&verts, v[0], v[1], v[2], subdiv);
+       }
+
+       int num_verts = (int)verts.size();
+
+       mesh->clear();
+       Vec3 *varr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_VERTEX, 3, num_verts, 0);
+       Vec3 *narr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_NORMAL, 3, num_verts, 0);
+       Vec3 *tarr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_TANGENT, 3, num_verts, 0);
+       Vec2 *uvarr = (Vec2*)mesh->set_attrib_data(MESH_ATTR_TEXCOORD, 2, num_verts, 0);
+
+       for(int i=0; i<num_verts; i++) {
+               *varr++ = verts[i] * rad;
+               *narr++ = verts[i];
+
+               float theta = atan2(verts[i].z, verts[i].x);
+               float phi = acos(verts[i].y);
+
+               *tarr++ = normalize(sphvec(theta + 0.1f, (float)M_PI / 2.0f) - sphvec(theta - 0.1f, (float)M_PI / 2.0f));
+
+               float u = 0.5 * theta / M_PI + 0.5;
+               float v = phi / M_PI;
+               *uvarr++ = Vec2(u, v);
+       }
+}
+
+// -------- torus -----------
+static Vec3 torusvec(float theta, float phi, float mr, float rr)
+{
+       theta = -theta;
+
+       float rx = -cos(phi) * rr + mr;
+       float ry = sin(phi) * rr;
+       float rz = 0.0;
+
+       float x = rx * sin(theta) + rz * cos(theta);
+       float y = ry;
+       float z = -rx * cos(theta) + rz * sin(theta);
+
+       return Vec3(x, y, z);
+}
+
+void gen_torus(Mesh *mesh, float mainrad, float ringrad, int usub, int vsub, float urange, float vrange)
+{
+       if(usub < 4) usub = 4;
+       if(vsub < 2) vsub = 2;
+
+       int uverts = usub + 1;
+       int vverts = vsub + 1;
+
+       int num_verts = uverts * vverts;
+       int num_quads = usub * vsub;
+       int num_tri = num_quads * 2;
+
+       mesh->clear();
+       Vec3 *varr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_VERTEX, 3, num_verts, 0);
+       Vec3 *narr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_NORMAL, 3, num_verts, 0);
+       Vec3 *tarr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_TANGENT, 3, num_verts, 0);
+       Vec2 *uvarr = (Vec2*)mesh->set_attrib_data(MESH_ATTR_TEXCOORD, 2, num_verts, 0);
+       unsigned int *idxarr = mesh->set_index_data(num_tri * 3, 0);
+
+       float du = urange / (float)(uverts - 1);
+       float dv = vrange / (float)(vverts - 1);
+
+       float u = 0.0;
+       for(int i=0; i<uverts; i++) {
+               float theta = u * 2.0 * M_PI;
+
+               float v = 0.0;
+               for(int j=0; j<vverts; j++) {
+                       float phi = v * 2.0 * M_PI;
+
+                       Vec3 pos = torusvec(theta, phi, mainrad, ringrad);
+                       Vec3 cent = torusvec(theta, phi, mainrad, 0.0);
+
+                       *varr++ = pos;
+                       *narr++ = (pos - cent) / ringrad;
+
+                       Vec3 pprev = torusvec(theta - 0.1f, phi, mainrad, ringrad);
+                       Vec3 pnext = torusvec(theta + 0.1f, phi, mainrad, ringrad);
+
+                       *tarr++ = normalize(pnext - pprev);
+                       *uvarr++ = Vec2(u * urange, v * vrange);
+
+                       if(i < usub && j < vsub) {
+                               int idx = i * vverts + j;
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + 1;
+                               *idxarr++ = idx + vverts + 1;
+
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + vverts + 1;
+                               *idxarr++ = idx + vverts;
+                       }
+
+                       v += dv;
+               }
+               u += du;
+       }
+}
+
+
+// -------- cylinder --------
+
+static Vec3 cylvec(float theta, float height)
+{
+       return Vec3(sin(theta), height, cos(theta));
+}
+
+void gen_cylinder(Mesh *mesh, float rad, float height, int usub, int vsub, int capsub, float urange, float vrange)
+{
+       if(usub < 4) usub = 4;
+       if(vsub < 1) vsub = 1;
+
+       int uverts = usub + 1;
+       int vverts = vsub + 1;
+
+       int num_body_verts = uverts * vverts;
+       int num_body_quads = usub * vsub;
+       int num_body_tri = num_body_quads * 2;
+
+       int capvverts = capsub ? capsub + 1 : 0;
+       int num_cap_verts = uverts * capvverts;
+       int num_cap_quads = usub * capsub;
+       int num_cap_tri = num_cap_quads * 2;
+
+       int num_verts = num_body_verts + num_cap_verts * 2;
+       int num_tri = num_body_tri + num_cap_tri * 2;
+
+       mesh->clear();
+       Vec3 *varr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_VERTEX, 3, num_verts, 0);
+       Vec3 *narr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_NORMAL, 3, num_verts, 0);
+       Vec3 *tarr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_TANGENT, 3, num_verts, 0);
+       Vec2 *uvarr = (Vec2*)mesh->set_attrib_data(MESH_ATTR_TEXCOORD, 2, num_verts, 0);
+       unsigned int *idxarr = mesh->set_index_data(num_tri * 3, 0);
+
+       float du = urange / (float)(uverts - 1);
+       float dv = vrange / (float)(vverts - 1);
+
+       float u = 0.0;
+       for(int i=0; i<uverts; i++) {
+               float theta = SURAD(u);
+
+               float v = 0.0;
+               for(int j=0; j<vverts; j++) {
+                       float y = (v - 0.5) * height;
+                       Vec3 pos = cylvec(theta, y);
+
+                       *varr++ = Vec3(pos.x * rad, pos.y, pos.z * rad);
+                       *narr++ = Vec3(pos.x, 0.0, pos.z);
+                       *tarr++ = normalize(cylvec(theta + 0.1, 0.0) - cylvec(theta - 0.1, 0.0));
+                       *uvarr++ = Vec2(u * urange, v * vrange);
+
+                       if(i < usub && j < vsub) {
+                               int idx = i * vverts + j;
+
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + vverts + 1;
+                               *idxarr++ = idx + 1;
+
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + vverts;
+                               *idxarr++ = idx + vverts + 1;
+                       }
+
+                       v += dv;
+               }
+               u += du;
+       }
+
+
+       // now the cap!
+       if(!capsub) {
+               return;
+       }
+
+       dv = 1.0 / (float)(capvverts - 1);
+
+       u = 0.0;
+       for(int i=0; i<uverts; i++) {
+               float theta = SURAD(u);
+
+               float v = 0.0;
+               for(int j=0; j<capvverts; j++) {
+                       float r = v * rad;
+
+                       Vec3 pos = cylvec(theta, height / 2.0) * r;
+                       pos.y = height / 2.0;
+                       Vec3 tang = normalize(cylvec(theta + 0.1, 0.0) - cylvec(theta - 0.1, 0.0));
+
+                       *varr++ = pos;
+                       *narr++ = Vec3(0, 1, 0);
+                       *tarr++ = tang;
+                       *uvarr++ = Vec2(u * urange, v);
+
+                       pos.y = -height / 2.0;
+                       *varr++ = pos;
+                       *narr++ = Vec3(0, -1, 0);
+                       *tarr++ = -tang;
+                       *uvarr++ = Vec2(u * urange, v);
+
+                       if(i < usub && j < capsub) {
+                               unsigned int idx = num_body_verts + (i * capvverts + j) * 2;
+
+                               unsigned int vidx[4] = {
+                                       idx,
+                                       idx + capvverts * 2,
+                                       idx + (capvverts + 1) * 2,
+                                       idx + 2
+                               };
+
+                               *idxarr++ = vidx[0];
+                               *idxarr++ = vidx[2];
+                               *idxarr++ = vidx[1];
+                               *idxarr++ = vidx[0];
+                               *idxarr++ = vidx[3];
+                               *idxarr++ = vidx[2];
+
+                               *idxarr++ = vidx[0] + 1;
+                               *idxarr++ = vidx[1] + 1;
+                               *idxarr++ = vidx[2] + 1;
+                               *idxarr++ = vidx[0] + 1;
+                               *idxarr++ = vidx[2] + 1;
+                               *idxarr++ = vidx[3] + 1;
+                       }
+
+                       v += dv;
+               }
+               u += du;
+       }
+}
+
+// -------- cone --------
+
+static Vec3 conevec(float theta, float y, float height)
+{
+       float scale = 1.0 - y / height;
+       return Vec3(sin(theta) * scale, y, cos(theta) * scale);
+}
+
+void gen_cone(Mesh *mesh, float rad, float height, int usub, int vsub, int capsub, float urange, float vrange)
+{
+       if(usub < 4) usub = 4;
+       if(vsub < 1) vsub = 1;
+
+       int uverts = usub + 1;
+       int vverts = vsub + 1;
+
+       int num_body_verts = uverts * vverts;
+       int num_body_quads = usub * vsub;
+       int num_body_tri = num_body_quads * 2;
+
+       int capvverts = capsub ? capsub + 1 : 0;
+       int num_cap_verts = uverts * capvverts;
+       int num_cap_quads = usub * capsub;
+       int num_cap_tri = num_cap_quads * 2;
+
+       int num_verts = num_body_verts + num_cap_verts;
+       int num_tri = num_body_tri + num_cap_tri;
+
+       mesh->clear();
+       Vec3 *varr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_VERTEX, 3, num_verts, 0);
+       Vec3 *narr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_NORMAL, 3, num_verts, 0);
+       Vec3 *tarr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_TANGENT, 3, num_verts, 0);
+       Vec2 *uvarr = (Vec2*)mesh->set_attrib_data(MESH_ATTR_TEXCOORD, 2, num_verts, 0);
+       unsigned int *idxarr = mesh->set_index_data(num_tri * 3, 0);
+
+       float du = urange / (float)(uverts - 1);
+       float dv = vrange / (float)(vverts - 1);
+
+       float u = 0.0;
+       for(int i=0; i<uverts; i++) {
+               float theta = SURAD(u);
+
+               float v = 0.0;
+               for(int j=0; j<vverts; j++) {
+                       float y = v * height;
+                       Vec3 pos = conevec(theta, y, height);
+
+                       Vec3 tang = normalize(conevec(theta + 0.1, 0.0, height) - conevec(theta - 0.1, 0.0, height));
+                       Vec3 bitang = normalize(conevec(theta, y + 0.1, height) - pos);
+
+                       *varr++ = Vec3(pos.x * rad, pos.y, pos.z * rad);
+                       *narr++ = cross(tang, bitang);
+                       *tarr++ = tang;
+                       *uvarr++ = Vec2(u * urange, v * vrange);
+
+                       if(i < usub && j < vsub) {
+                               int idx = i * vverts + j;
+
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + vverts + 1;
+                               *idxarr++ = idx + 1;
+
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + vverts;
+                               *idxarr++ = idx + vverts + 1;
+                       }
+
+                       v += dv;
+               }
+               u += du;
+       }
+
+
+       // now the bottom cap!
+       if(!capsub) {
+               return;
+       }
+
+       dv = 1.0 / (float)(capvverts - 1);
+
+       u = 0.0;
+       for(int i=0; i<uverts; i++) {
+               float theta = SURAD(u);
+
+               float v = 0.0;
+               for(int j=0; j<capvverts; j++) {
+                       float r = v * rad;
+
+                       Vec3 pos = conevec(theta, 0.0, height) * r;
+                       Vec3 tang = normalize(cylvec(theta + 0.1, 0.0) - cylvec(theta - 0.1, 0.0));
+
+                       *varr++ = pos;
+                       *narr++ = Vec3(0, -1, 0);
+                       *tarr++ = tang;
+                       *uvarr++ = Vec2(u * urange, v);
+
+                       if(i < usub && j < capsub) {
+                               unsigned int idx = num_body_verts + i * capvverts + j;
+
+                               unsigned int vidx[4] = {
+                                       idx,
+                                       idx + capvverts,
+                                       idx + (capvverts + 1),
+                                       idx + 1
+                               };
+
+                               *idxarr++ = vidx[0];
+                               *idxarr++ = vidx[1];
+                               *idxarr++ = vidx[2];
+                               *idxarr++ = vidx[0];
+                               *idxarr++ = vidx[2];
+                               *idxarr++ = vidx[3];
+                       }
+
+                       v += dv;
+               }
+               u += du;
+       }
+}
+
+
+// -------- plane --------
+
+void gen_plane(Mesh *mesh, float width, float height, int usub, int vsub)
+{
+       gen_heightmap(mesh, width, height, usub, vsub, 0);
+}
+
+
+// ----- heightmap ------
+
+void gen_heightmap(Mesh *mesh, float width, float height, int usub, int vsub, float (*hf)(float, float, void*), void *hfdata)
+{
+       if(usub < 1) usub = 1;
+       if(vsub < 1) vsub = 1;
+
+       mesh->clear();
+
+       int uverts = usub + 1;
+       int vverts = vsub + 1;
+       int num_verts = uverts * vverts;
+
+       int num_quads = usub * vsub;
+       int num_tri = num_quads * 2;
+
+       Vec3 *varr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_VERTEX, 3, num_verts, 0);
+       Vec3 *narr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_NORMAL, 3, num_verts, 0);
+       Vec3 *tarr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_TANGENT, 3, num_verts, 0);
+       Vec2 *uvarr = (Vec2*)mesh->set_attrib_data(MESH_ATTR_TEXCOORD, 2, num_verts, 0);
+       unsigned int *idxarr = mesh->set_index_data(num_tri * 3, 0);
+
+       float du = 1.0 / (float)usub;
+       float dv = 1.0 / (float)vsub;
+
+       float u = 0.0;
+       for(int i=0; i<uverts; i++) {
+               float v = 0.0;
+               for(int j=0; j<vverts; j++) {
+                       float x = (u - 0.5) * width;
+                       float y = (v - 0.5) * height;
+                       float z = hf ? hf(u, v, hfdata) : 0.0;
+
+                       Vec3 normal = Vec3(0, 0, 1);
+                       if(hf) {
+                               float u1z = hf(u + du, v, hfdata);
+                               float v1z = hf(u, v + dv, hfdata);
+
+                               Vec3 tang = Vec3(du * width, 0, u1z - z);
+                               Vec3 bitan = Vec3(0, dv * height, v1z - z);
+                               normal = normalize(cross(tang, bitan));
+                       }
+
+                       *varr++ = Vec3(x, y, z);
+                       *narr++ = normal;
+                       *tarr++ = Vec3(1, 0, 0);
+                       *uvarr++ = Vec2(u, v);
+
+                       if(i < usub && j < vsub) {
+                               int idx = i * vverts + j;
+
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + vverts + 1;
+                               *idxarr++ = idx + 1;
+
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + vverts;
+                               *idxarr++ = idx + vverts + 1;
+                       }
+
+                       v += dv;
+               }
+               u += du;
+       }
+}
+
+// ----- box ------
+void gen_box(Mesh *mesh, float xsz, float ysz, float zsz, int usub, int vsub)
+{
+       static const float face_angles[][2] = {
+               {0, 0},
+               {M_PI / 2.0, 0},
+               {M_PI, 0},
+               {3.0 * M_PI / 2.0, 0},
+               {0, M_PI / 2.0},
+               {0, -M_PI / 2.0}
+       };
+
+       if(usub < 1) usub = 1;
+       if(vsub < 1) vsub = 1;
+
+       mesh->clear();
+
+       for(int i=0; i<6; i++) {
+               Mat4 xform, dir_xform;
+               Mesh m;
+
+               gen_plane(&m, 1, 1, usub, vsub);
+               xform.translate(Vec3(0, 0, 0.5));
+               xform.rotate(Vec3(face_angles[i][1], face_angles[i][0], 0));
+               dir_xform = xform;
+               m.apply_xform(xform, dir_xform);
+
+               mesh->append(m);
+       }
+
+       Mat4 scale;
+       scale.scaling(xsz, ysz, zsz);
+       mesh->apply_xform(scale, Mat4::identity);
+}
+
+/*
+void gen_box(Mesh *mesh, float xsz, float ysz, float zsz)
+{
+       mesh->clear();
+
+       const int num_faces = 6;
+       int num_verts = num_faces * 4;
+       int num_tri = num_faces * 2;
+
+       float x = xsz / 2.0;
+       float y = ysz / 2.0;
+       float z = zsz / 2.0;
+
+       Vec3 *varr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_VERTEX, 3, num_verts, 0);
+       Vec3 *narr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_NORMAL, 3, num_verts, 0);
+       Vec3 *tarr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_TANGENT, 3, num_verts, 0);
+       Vec2 *uvarr = (Vec2*)mesh->set_attrib_data(MESH_ATTR_TEXCOORD, 2, num_verts, 0);
+       unsigned int *idxarr = mesh->set_index_data(num_tri * 3, 0);
+
+       static const Vec2 uv[] = { Vec2(0, 0), Vec2(1, 0), Vec2(1, 1), Vec2(0, 1) };
+
+       // front
+       for(int i=0; i<4; i++) {
+               *narr++ = Vec3(0, 0, 1);
+               *tarr++ = Vec3(1, 0, 0);
+               *uvarr++ = uv[i];
+       }
+       *varr++ = Vec3(-x, -y, z);
+       *varr++ = Vec3(x, -y, z);
+       *varr++ = Vec3(x, y, z);
+       *varr++ = Vec3(-x, y, z);
+       // right
+       for(int i=0; i<4; i++) {
+               *narr++ = Vec3(1, 0, 0);
+               *tarr++ = Vec3(0, 0, -1);
+               *uvarr++ = uv[i];
+       }
+       *varr++ = Vec3(x, -y, z);
+       *varr++ = Vec3(x, -y, -z);
+       *varr++ = Vec3(x, y, -z);
+       *varr++ = Vec3(x, y, z);
+       // back
+       for(int i=0; i<4; i++) {
+               *narr++ = Vec3(0, 0, -1);
+               *tarr++ = Vec3(-1, 0, 0);
+               *uvarr++ = uv[i];
+       }
+       *varr++ = Vec3(x, -y, -z);
+       *varr++ = Vec3(-x, -y, -z);
+       *varr++ = Vec3(-x, y, -z);
+       *varr++ = Vec3(x, y, -z);
+       // left
+       for(int i=0; i<4; i++) {
+               *narr++ = Vec3(-1, 0, 0);
+               *tarr++ = Vec3(0, 0, 1);
+               *uvarr++ = uv[i];
+       }
+       *varr++ = Vec3(-x, -y, -z);
+       *varr++ = Vec3(-x, -y, z);
+       *varr++ = Vec3(-x, y, z);
+       *varr++ = Vec3(-x, y, -z);
+       // top
+       for(int i=0; i<4; i++) {
+               *narr++ = Vec3(0, 1, 0);
+               *tarr++ = Vec3(1, 0, 0);
+               *uvarr++ = uv[i];
+       }
+       *varr++ = Vec3(-x, y, z);
+       *varr++ = Vec3(x, y, z);
+       *varr++ = Vec3(x, y, -z);
+       *varr++ = Vec3(-x, y, -z);
+       // bottom
+       for(int i=0; i<4; i++) {
+               *narr++ = Vec3(0, -1, 0);
+               *tarr++ = Vec3(1, 0, 0);
+               *uvarr++ = uv[i];
+       }
+       *varr++ = Vec3(-x, -y, -z);
+       *varr++ = Vec3(x, -y, -z);
+       *varr++ = Vec3(x, -y, z);
+       *varr++ = Vec3(-x, -y, z);
+
+       // index array
+       static const int faceidx[] = {0, 1, 2, 0, 2, 3};
+       for(int i=0; i<num_faces; i++) {
+               for(int j=0; j<6; j++) {
+                       *idxarr++ = faceidx[j] + i * 4;
+               }
+       }
+}
+*/
+
+static inline Vec3 rev_vert(float u, float v, Vec2 (*rf)(float, float, void*), void *cls)
+{
+       Vec2 pos = rf(u, v, cls);
+
+       float angle = u * 2.0 * M_PI;
+       float x = pos.x * cos(angle);
+       float y = pos.y;
+       float z = pos.x * sin(angle);
+
+       return Vec3(x, y, z);
+}
+
+// ------ surface of revolution -------
+void gen_revol(Mesh *mesh, int usub, int vsub, Vec2 (*rfunc)(float, float, void*), void *cls)
+{
+       gen_revol(mesh, usub, vsub, rfunc, 0, cls);
+}
+
+void gen_revol(Mesh *mesh, int usub, int vsub, Vec2 (*rfunc)(float, float, void*),
+               Vec2 (*nfunc)(float, float, void*), void *cls)
+{
+       if(!rfunc) return;
+       if(usub < 3) usub = 3;
+       if(vsub < 1) vsub = 1;
+
+       mesh->clear();
+
+       int uverts = usub + 1;
+       int vverts = vsub + 1;
+       int num_verts = uverts * vverts;
+
+       int num_quads = usub * vsub;
+       int num_tri = num_quads * 2;
+
+       Vec3 *varr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_VERTEX, 3, num_verts, 0);
+       Vec3 *narr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_NORMAL, 3, num_verts, 0);
+       Vec3 *tarr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_TANGENT, 3, num_verts, 0);
+       Vec2 *uvarr = (Vec2*)mesh->set_attrib_data(MESH_ATTR_TEXCOORD, 2, num_verts, 0);
+       unsigned int *idxarr = mesh->set_index_data(num_tri * 3, 0);
+
+       float du = 1.0 / (float)(uverts - 1);
+       float dv = 1.0 / (float)(vverts - 1);
+
+       float u = 0.0;
+       for(int i=0; i<uverts; i++) {
+               float v = 0.0;
+               for(int j=0; j<vverts; j++) {
+                       Vec3 pos = rev_vert(u, v, rfunc, cls);
+
+                       Vec3 nextu = rev_vert(fmod(u + du, 1.0), v, rfunc, cls);
+                       Vec3 tang = nextu - pos;
+                       if(length_sq(tang) < 1e-6) {
+                               float new_v = v > 0.5 ? v - dv * 0.25 : v + dv * 0.25;
+                               nextu = rev_vert(fmod(u + du, 1.0), new_v, rfunc, cls);
+                               tang = nextu - pos;
+                       }
+
+                       Vec3 normal;
+                       if(nfunc) {
+                               normal = rev_vert(u, v, nfunc, cls);
+                       } else {
+                               Vec3 nextv = rev_vert(u, v + dv, rfunc, cls);
+                               Vec3 bitan = nextv - pos;
+                               if(length_sq(bitan) < 1e-6) {
+                                       nextv = rev_vert(u, v - dv, rfunc, cls);
+                                       bitan = pos - nextv;
+                               }
+
+                               normal = cross(tang, bitan);
+                       }
+
+                       *varr++ = pos;
+                       *narr++ = normalize(normal);
+                       *tarr++ = normalize(tang);
+                       *uvarr++ = Vec2(u, v);
+
+                       if(i < usub && j < vsub) {
+                               int idx = i * vverts + j;
+
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + vverts + 1;
+                               *idxarr++ = idx + 1;
+
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + vverts;
+                               *idxarr++ = idx + vverts + 1;
+                       }
+
+                       v += dv;
+               }
+               u += du;
+       }
+}
+
+
+static inline Vec3 sweep_vert(float u, float v, float height, Vec2 (*sf)(float, float, void*), void *cls)
+{
+       Vec2 pos = sf(u, v, cls);
+
+       float x = pos.x;
+       float y = v * height;
+       float z = pos.y;
+
+       return Vec3(x, y, z);
+}
+
+// ---- sweep shape along a path ----
+void gen_sweep(Mesh *mesh, float height, int usub, int vsub, Vec2 (*sfunc)(float, float, void*), void *cls)
+{
+       if(!sfunc) return;
+       if(usub < 3) usub = 3;
+       if(vsub < 1) vsub = 1;
+
+       mesh->clear();
+
+       int uverts = usub + 1;
+       int vverts = vsub + 1;
+       int num_verts = uverts * vverts;
+
+       int num_quads = usub * vsub;
+       int num_tri = num_quads * 2;
+
+       Vec3 *varr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_VERTEX, 3, num_verts, 0);
+       Vec3 *narr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_NORMAL, 3, num_verts, 0);
+       Vec3 *tarr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_TANGENT, 3, num_verts, 0);
+       Vec2 *uvarr = (Vec2*)mesh->set_attrib_data(MESH_ATTR_TEXCOORD, 2, num_verts, 0);
+       unsigned int *idxarr = mesh->set_index_data(num_tri * 3, 0);
+
+       float du = 1.0 / (float)(uverts - 1);
+       float dv = 1.0 / (float)(vverts - 1);
+
+       float u = 0.0;
+       for(int i=0; i<uverts; i++) {
+               float v = 0.0;
+               for(int j=0; j<vverts; j++) {
+                       Vec3 pos = sweep_vert(u, v, height, sfunc, cls);
+
+                       Vec3 nextu = sweep_vert(fmod(u + du, 1.0), v, height, sfunc, cls);
+                       Vec3 tang = nextu - pos;
+                       if(length_sq(tang) < 1e-6) {
+                               float new_v = v > 0.5 ? v - dv * 0.25 : v + dv * 0.25;
+                               nextu = sweep_vert(fmod(u + du, 1.0), new_v, height, sfunc, cls);
+                               tang = nextu - pos;
+                       }
+
+                       Vec3 normal;
+                       Vec3 nextv = sweep_vert(u, v + dv, height, sfunc, cls);
+                       Vec3 bitan = nextv - pos;
+                       if(length_sq(bitan) < 1e-6) {
+                               nextv = sweep_vert(u, v - dv, height, sfunc, cls);
+                               bitan = pos - nextv;
+                       }
+
+                       normal = cross(tang, bitan);
+
+                       *varr++ = pos;
+                       *narr++ = normalize(normal);
+                       *tarr++ = normalize(tang);
+                       *uvarr++ = Vec2(u, v);
+
+                       if(i < usub && j < vsub) {
+                               int idx = i * vverts + j;
+
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + vverts + 1;
+                               *idxarr++ = idx + 1;
+
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + vverts;
+                               *idxarr++ = idx + vverts + 1;
+                       }
+
+                       v += dv;
+               }
+               u += du;
+       }
+}
diff --git a/src/meshgen.h b/src/meshgen.h
new file mode 100644 (file)
index 0000000..f154c24
--- /dev/null
@@ -0,0 +1,23 @@
+#ifndef MESHGEN_H_
+#define MESHGEN_H_
+
+#include "gmath/gmath.h"
+
+class Mesh;
+
+void gen_sphere(Mesh *mesh, float rad, int usub, int vsub, float urange = 1.0, float vrange = 1.0);
+void gen_geosphere(Mesh *mesh, float rad, int subdiv, bool hemi = false);
+void gen_torus(Mesh *mesh, float mainrad, float ringrad, int usub, int vsub, float urange = 1.0, float vrange = 1.0);
+void gen_cylinder(Mesh *mesh, float rad, float height, int usub, int vsub, int capsub = 0, float urange = 1.0, float vrange = 1.0);
+void gen_cone(Mesh *mesh, float rad, float height, int usub, int vsub, int capsub = 0, float urange = 1.0, float vrange = 1.0);
+void gen_plane(Mesh *mesh, float width, float height, int usub = 1, int vsub = 1);
+void gen_heightmap(Mesh *mesh, float width, float height, int usub, int vsub, float (*hf)(float, float, void*), void *hfdata = 0);
+void gen_box(Mesh *mesh, float xsz, float ysz, float zsz, int usub = 1, int vsub = 1);
+
+void gen_revol(Mesh *mesh, int usub, int vsub, Vec2 (*rfunc)(float, float, void*), void *cls = 0);
+void gen_revol(Mesh *mesh, int usub, int vsub, Vec2 (*rfunc)(float, float, void*), Vec2 (*nfunc)(float, float, void*), void *cls);
+
+/* callback args: (float u, float v, void *cls) -> Vec2 XZ offset u,v in [0, 1] */
+void gen_sweep(Mesh *mesh, float height, int usub, int vsub, Vec2 (*sfunc)(float, float, void*), void *cls = 0);
+
+#endif // MESHGEN_H_
diff --git a/src/metascene.cc b/src/metascene.cc
new file mode 100644 (file)
index 0000000..e0b2b20
--- /dev/null
@@ -0,0 +1,353 @@
+#include <string>
+#include <regex>
+#include "metascene.h"
+#include "scene.h"
+#include "treestore.h"
+#include "logger.h"
+#include "app.h"
+
+#ifdef WIN32
+#include <malloc.h>
+#else
+#include <alloca.h>
+#endif
+
+struct MaterialEdit {
+       std::regex name_re;
+       int attr;
+       Texture *tex;
+};
+
+static bool proc_node(MetaScene *mscn, struct ts_node *node);
+static bool proc_scenefile(MetaScene *mscn, struct ts_node *node);
+static bool proc_mtledit(MetaScene *mscn, MaterialEdit *med, struct ts_node *node);
+static void apply_mtledit(Scene *scn, const MaterialEdit &med);
+static void apply_mtledit(Material *mtl, const MaterialEdit &med);
+static struct ts_attr *attr_inscope(struct ts_node *node, const char *name);
+
+static void print_scene_graph(SceneNode *n, int level);
+
+
+MetaScene::MetaScene()
+{
+       walk_mesh = 0;
+}
+
+MetaScene::~MetaScene()
+{
+       delete walk_mesh;
+}
+
+
+bool MetaScene::load(const char *fname)
+{
+       struct ts_node *root = ts_load(fname);
+       if(!root || strcmp(root->name, "scene") != 0) {
+               ts_free_tree(root);
+               error_log("failed to load scene metadata: %s\n", fname);
+               return false;
+       }
+
+       bool res = proc_node(this, root);
+       ts_free_tree(root);
+
+       /*info_log("loaded scene: %s\n", fname);
+       info_log("scene graph:\n");
+       print_scene_graph(scn->nodes, 0);
+       */
+       return res;
+}
+
+void MetaScene::update(float dt)
+{
+       int nscn = scenes.size();
+       for(int i=0; i<nscn; i++) {
+               scenes[i]->update(dt);
+       }
+}
+
+void MetaScene::draw() const
+{
+       int nscn = scenes.size();
+       for(int i=0; i<nscn; i++) {
+               scenes[i]->draw();
+       }
+}
+
+static bool proc_node(MetaScene *mscn, struct ts_node *node)
+{
+       struct ts_node *c = node->child_list;
+       while(c) {
+               if(!proc_node(mscn, c)) {
+                       return false;
+               }
+               c = c->next;
+       }
+
+       // do this last to allow other contents of the node to do their thing
+       if(strcmp(node->name, "scenefile") == 0) {
+               return proc_scenefile(mscn, node);
+
+       } else if(strcmp(node->name, "remap") == 0) {
+               const char *match = ts_get_attr_str(node, "match");
+               const char *replace = ts_get_attr_str(node, "replace");
+               if(match && replace) {
+                       mscn->datamap.map(match, replace);
+               }
+       }
+
+       return true;
+}
+
+
+
+struct SceneData {
+       MetaScene *meta;
+       std::string walkmesh_regexp, spawn_regexp;
+       std::vector<MaterialEdit> mtledit;
+};
+
+static bool proc_scenefile(MetaScene *mscn, struct ts_node *node)
+{
+       const char *fname = ts_get_attr_str(node, "file");
+       if(fname) {
+               SceneData *sdat = new SceneData;
+               sdat->meta = mscn;
+
+               // datapath
+               struct ts_attr *adpath = attr_inscope(node, "datapath");
+               if(adpath && adpath->val.type == TS_STRING) {
+                       info_log("adding data path: %s\n", adpath->val.str);
+                       mscn->datamap.set_path(adpath->val.str);
+               }
+
+               // walkmesh
+               struct ts_attr *awmesh = attr_inscope(node, "walkmesh");
+               if(awmesh && awmesh->val.type == TS_STRING) {
+                       sdat->walkmesh_regexp = std::string(awmesh->val.str);
+               }
+
+               // spawn node
+               struct ts_attr *awspawn = attr_inscope(node, "spawn");
+               if(awspawn) {
+                       switch(awspawn->val.type) {
+                       case TS_VECTOR:
+                               mscn->start_pos = Vec3(awspawn->val.vec[0], awspawn->val.vec[1],
+                                               awspawn->val.vec[2]);
+                               break;
+
+                       case TS_STRING:
+                       default:
+                               sdat->spawn_regexp = std::string(awspawn->val.str);
+                       }
+               }
+               if((awspawn = attr_inscope(node, "spawn_rot")) && awspawn->val.type == TS_VECTOR) {
+                       Quat rot;
+                       rot.rotate(Vec3(1, 0, 0), deg_to_rad(awspawn->val.vec[0]));
+                       rot.rotate(Vec3(0, 1, 0), deg_to_rad(awspawn->val.vec[1]));
+                       rot.rotate(Vec3(0, 0, 1), deg_to_rad(awspawn->val.vec[2]));
+                       mscn->start_rot = rot;
+               }
+
+               int namesz = mscn->datamap.lookup(fname, 0, 0);
+               char *namebuf = (char*)alloca(namesz + 1);
+               if(mscn->datamap.lookup(fname, namebuf, namesz + 1)) {
+                       fname = namebuf;
+               }
+
+               // material edits
+               struct ts_node *child = node->child_list;
+               while(child) {
+                       MaterialEdit medit;
+                       if(proc_mtledit(mscn, &medit, child)) {
+                               sdat->mtledit.push_back(medit);
+                       }
+                       child = child->next;
+               }
+
+               Scene *newscn = sceneman.get(fname);
+               /* NOTE: setting all these after get() is not a race condition, because
+                * scene_loaded() which uses this, will only run in our main loop during
+                * SceneSet::update() on the main thread.
+                */
+               newscn->datamap = mscn->datamap;
+               mscn->datamap.clear();
+
+               newscn->metascn = mscn;
+               mscn->scndata[newscn] = sdat;
+       }
+       return true;
+}
+
+bool MetaScene::scene_loaded(Scene *newscn)
+{
+       SceneData *sdat = (SceneData*)scndata[newscn];
+       if(!sdat) {
+               error_log("MetaScene::scene_loaded called, but corresponding SceneData not found\n");
+               return false;
+       }
+
+       // extract the walk mesh if necessary
+       Scene *wscn;
+       if(!sdat->walkmesh_regexp.empty() && (wscn = newscn->extract_nodes(sdat->walkmesh_regexp.c_str()))) {
+               // apply all transformations to the meshes
+               wscn->apply_xform();
+
+               int nmeshes = wscn->meshes.size();
+               for(int i=0; i<nmeshes; i++) {
+                       Mesh *m = wscn->meshes[i];
+
+                       if(walk_mesh) {
+                               walk_mesh->append(*m);
+                       } else {
+                               walk_mesh = m;
+                               wscn->remove_mesh(m);   // to save it from destruction
+                       }
+               }
+
+               delete wscn;
+       }
+
+       // extract the spawn node
+       if(!sdat->spawn_regexp.empty() && (wscn = newscn->extract_nodes(sdat->spawn_regexp.c_str()))) {
+
+               int nmeshes = wscn->meshes.size();
+               int nnodes = wscn->nodes ? wscn->nodes->get_num_children() : 0;
+
+               if(nmeshes) {
+                       Vec3 pos;
+                       for(int i=0; i<nmeshes; i++) {
+                               const Sphere &bsph = wscn->meshes[i]->get_bsphere();
+                               pos += bsph.center;
+                       }
+                       pos /= (float)nmeshes;
+                       sdat->meta->start_pos = pos;
+
+               } else if(nnodes) {
+                       // just use the first one
+                       SceneNode *first = wscn->nodes->get_child(0);
+                       sdat->meta->start_pos = first->get_position();
+                       sdat->meta->start_rot = first->get_rotation();
+               }
+               delete wscn;
+       }
+
+       int num_medits = sdat->mtledit.size();
+       for(int i=0; i<num_medits; i++) {
+               // perform material edits
+               apply_mtledit(newscn, sdat->mtledit[i]);
+       }
+
+       scenes.push_back(newscn);
+       return true;
+}
+
+static bool proc_mtledit(MetaScene *mscn, MaterialEdit *med, struct ts_node *node)
+{
+       if(strcmp(node->name, "mtledit") != 0) {
+               return false;
+       }
+
+       const char *restr = ".*";
+       struct ts_attr *amtl = ts_get_attr(node, "material");
+       if(amtl && amtl->val.type == TS_STRING) {
+               restr = amtl->val.str;
+       }
+
+       med->name_re = std::regex(restr);
+
+       node = node->child_list;
+       while(node) {
+               struct ts_node *cn = node;
+               node = node->next;
+
+               if(strcmp(cn->name, "texture") == 0) {
+                       // add/change/remove a texture
+                       struct ts_attr *atype = ts_get_attr(cn, "type");
+                       struct ts_attr *afile = ts_get_attr(cn, "file");
+
+                       int textype = MTL_TEX_DIFFUSE;
+                       if(atype) {
+                               if(atype->val.type == TS_STRING) {
+                                       textype = mtl_parse_type(atype->val.str);
+                               } else if(atype->val.type == TS_NUMBER) {
+                                       textype = atype->val.inum;
+                                       if(textype < 0 || textype >= NUM_MTL_TEXTURES) {
+                                               error_log("invalid texture in mtledit: %d\n", textype);
+                                               continue;
+                                       }
+                               } else {
+                                       error_log("unexpected texture type in mtledit: %s\n", atype->val.str);
+                                       continue;
+                               }
+                       }
+
+                       med->attr = textype;
+
+                       if(!afile || !afile->val.str || !*afile->val.str) {
+                               // remove
+                               med->tex = 0;
+                       } else {
+                               med->tex = texman.get_texture(afile->val.str, TEX_2D, &mscn->datamap);
+                       }
+               }
+               // TODO add more edit modes
+       }
+
+       return true;
+}
+
+static void apply_mtledit(Scene *scn, const MaterialEdit &med)
+{
+       // search all the objects to find matching material names
+       int nobj = scn->objects.size();
+       for(int i=0; i<nobj; i++) {
+               Object *obj = scn->objects[i];
+               if(std::regex_match(obj->get_name(), med.name_re)) {
+                       apply_mtledit(&obj->mtl, med);
+               }
+       }
+}
+
+static void apply_mtledit(Material *mtl, const MaterialEdit &med)
+{
+       // TODO more edit modes...
+       if(med.tex) {
+               mtl->add_texture(med.tex, med.attr);
+       } else {
+               Texture *tex = mtl->stdtex[med.attr];
+               if(tex) {
+                       mtl->remove_texture(tex);
+               }
+       }
+}
+
+static struct ts_attr *attr_inscope(struct ts_node *node, const char *name)
+{
+       struct ts_attr *attr = 0;
+
+       while(node && !(attr = ts_get_attr(node, name))) {
+               node = node->parent;
+       }
+       return attr;
+}
+
+static void print_scene_graph(SceneNode *n, int level)
+{
+       if(!n) return;
+
+       for(int i=0; i<level; i++) {
+               info_log("  ");
+       }
+
+       int nobj = n->get_num_objects();
+       if(nobj) {
+               info_log("%s - %d obj\n", n->get_name(), n->get_num_objects());
+       } else {
+               info_log("%s\n", n->get_name());
+       }
+
+       for(int i=0; i<n->get_num_children(); i++) {
+               print_scene_graph(n->get_child(i), level + 1);
+       }
+}
diff --git a/src/metascene.h b/src/metascene.h
new file mode 100644 (file)
index 0000000..8d0f5c2
--- /dev/null
@@ -0,0 +1,32 @@
+#ifndef METASCENE_H_
+#define METASCENE_H_
+
+#include <map>
+#include "scene.h"
+#include "mesh.h"
+#include "datamap.h"
+
+class MetaScene {
+public:
+       DataMap datamap;
+
+       std::vector<Scene*> scenes;
+
+       Mesh *walk_mesh;
+       Vec3 start_pos;
+       Quat start_rot;
+
+       std::map<Scene*, void*> scndata;
+
+
+       MetaScene();
+       ~MetaScene();
+
+       bool load(const char *fname);
+       bool scene_loaded(Scene *scn);
+
+       void update(float dt);
+       void draw() const;
+};
+
+#endif // METASCENE_H_
diff --git a/src/object.cc b/src/object.cc
new file mode 100644 (file)
index 0000000..d850d47
--- /dev/null
@@ -0,0 +1,37 @@
+#include "object.h"
+#include "snode.h"
+
+
+Object::Object()
+{
+       name = "<unnamed>";
+       node = 0;
+}
+
+ObjType Object::get_type() const
+{
+       return OBJ_NULL;
+}
+
+void Object::set_name(const char *name)
+{
+       this->name = name;
+}
+
+const char *Object::get_name() const
+{
+       return name.c_str();
+}
+
+bool Object::intersect(const Ray &ray, HitPoint *hit) const
+{
+       return false;
+}
+
+void Object::update(float dt)
+{
+}
+
+void Object::draw() const
+{
+}
diff --git a/src/object.h b/src/object.h
new file mode 100644 (file)
index 0000000..cc82d1b
--- /dev/null
@@ -0,0 +1,37 @@
+#ifndef OBJECT_H_
+#define OBJECT_H_
+
+#include <string>
+#include <gmath/gmath.h>
+#include "geom.h"
+#include "material.h"
+
+class Object;
+class SceneNode;
+
+enum ObjType { OBJ_NULL, OBJ_MESH };
+
+class Object {
+private:
+       std::string name;
+
+public:
+       Material mtl;
+       //GeomObject *bvol;
+       SceneNode *node;
+
+       Object();
+       virtual ~Object() = default;
+
+       virtual ObjType get_type() const;
+
+       virtual void set_name(const char *name);
+       virtual const char *get_name() const;
+
+       virtual bool intersect(const Ray &ray, HitPoint *hit = 0) const;
+
+       virtual void update(float dt = 0.0f);
+       virtual void draw() const;
+};
+
+#endif // OBJECT_H_
diff --git a/src/objmesh.cc b/src/objmesh.cc
new file mode 100644 (file)
index 0000000..3076e67
--- /dev/null
@@ -0,0 +1,33 @@
+#include "opengl.h"
+#include "objmesh.h"
+#include "snode.h"
+
+ObjMesh::ObjMesh()
+{
+       mesh = 0;
+}
+
+ObjType ObjMesh::get_type() const
+{
+       return OBJ_MESH;
+}
+
+void ObjMesh::draw() const
+{
+       if(!mesh) return;
+
+       mtl.setup();
+
+       if(node) {
+               glMatrixMode(GL_MODELVIEW);
+               glPushMatrix();
+               glMultMatrixf(node->get_matrix()[0]);
+       }
+
+       mesh->draw();
+
+       if(node) {
+               glMatrixMode(GL_MODELVIEW);
+               glPopMatrix();
+       }
+}
diff --git a/src/objmesh.h b/src/objmesh.h
new file mode 100644 (file)
index 0000000..811caf6
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef OBJMESH_H_
+#define OBJMESH_H_
+
+#include "object.h"
+#include "mesh.h"
+
+class ObjMesh : public Object {
+public:
+       Mesh *mesh;
+
+       ObjMesh();
+
+       ObjType get_type() const;
+
+       void draw() const;
+};
+
+#endif // OBJMESH_H_
diff --git a/src/opengl.h b/src/opengl.h
new file mode 100644 (file)
index 0000000..deeddd3
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef OPENGL_H_
+#define OPENGL_H_
+
+#include <GL/glew.h>
+
+#endif // OPENGL_H_
diff --git a/src/opt.cc b/src/opt.cc
new file mode 100644 (file)
index 0000000..18d73f2
--- /dev/null
@@ -0,0 +1,114 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <optcfg.h>
+#include "opt.h"
+
+Options opt;
+
+Options def_opt = {
+       1280, 800,
+       false,  // vr
+       false,  // fullscreen
+       0               // scene file
+};
+
+enum {
+       OPT_SIZE,
+       OPT_VR,
+       OPT_FULLSCREEN,
+       OPT_WINDOWED,
+       OPT_SCENEFILE,
+       OPT_HELP
+};
+
+static optcfg_option options[] = {
+       // short, long, id, desc
+       {'s', "size", OPT_SIZE, "window size (WxH)"},
+       {0, "vr", OPT_VR, "enable VR mode"},
+       {'f', "fullscreen", OPT_FULLSCREEN, "run in fullscreen mode"},
+       {'w', "windowed", OPT_WINDOWED, "run in windowed mode"},
+       {0, "scene", OPT_SCENEFILE, "scene file to open"},
+       {'h', "help", OPT_HELP, "print usage and exit"},
+       OPTCFG_OPTIONS_END
+};
+
+static int opt_handler(optcfg *oc, int opt, void *cls);
+static int arg_handler(optcfg *oc, const char *arg, void *cls);
+
+bool init_options(int argc, char **argv, const char *cfgfile)
+{
+       // default options
+       opt = def_opt;
+
+       optcfg *oc = optcfg_init(options);
+       optcfg_set_opt_callback(oc, opt_handler, 0);
+       optcfg_set_arg_callback(oc, arg_handler, 0);
+
+       if(cfgfile) {
+               optcfg_parse_config_file(oc, cfgfile);
+       }
+
+       if(argv && optcfg_parse_args(oc, argc, argv) == -1) {
+               fprintf(stderr, "invalid option\n");
+               optcfg_destroy(oc);
+               return false;
+       }
+
+       optcfg_destroy(oc);
+       return true;
+}
+
+static bool is_enabled(optcfg *oc)
+{
+       int res;
+       optcfg_enabled_value(oc, &res);
+       return res != 0;
+}
+
+static int opt_handler(optcfg *oc, int optid, void *cls)
+{
+       switch(optid) {
+       case OPT_SIZE:
+               {
+                       char *valstr = optcfg_next_value(oc);
+                       if(!valstr || sscanf(valstr, "%dx%d", &opt.width, &opt.height) != 2) {
+                               fprintf(stderr, "size must be in the form: WIDTHxHEIGHT\n");
+                               return -1;
+                       }
+               }
+               break;
+
+       case OPT_VR:
+               opt.vr = is_enabled(oc);
+               break;
+
+       case OPT_FULLSCREEN:
+               opt.fullscreen = is_enabled(oc);
+               break;
+
+       case OPT_WINDOWED:
+               opt.fullscreen = !is_enabled(oc);
+               break;
+
+       case OPT_SCENEFILE:
+               opt.scenefile = strdup(optcfg_next_value(oc));
+               break;
+
+       case OPT_HELP:
+               printf("Usage: vrfileman [options]\nOptions:\n");
+               optcfg_print_options(oc);
+               exit(0);
+       }
+       return 0;
+}
+
+static int arg_handler(optcfg *oc, const char *arg, void *cls)
+{
+       if(opt.scenefile) {
+               fprintf(stderr, "unexpected argument: %s\n", arg);
+               return -1;
+       }
+       opt.scenefile = arg;
+       return 0;
+}
diff --git a/src/opt.h b/src/opt.h
new file mode 100644 (file)
index 0000000..e173545
--- /dev/null
+++ b/src/opt.h
@@ -0,0 +1,15 @@
+#ifndef OPT_H_
+#define OPT_H_
+
+struct Options {
+       int width, height;
+       bool vr;
+       bool fullscreen;
+       const char *scenefile;
+};
+
+extern Options opt, def_opt;
+
+bool init_options(int argc, char **argv, const char *cfgfile);
+
+#endif // OPT_H_
diff --git a/src/post.cc b/src/post.cc
new file mode 100644 (file)
index 0000000..b675f5b
--- /dev/null
@@ -0,0 +1,67 @@
+#include "opengl.h"
+#include "app.h"
+
+static void update_fbtex();
+
+static unsigned int fb_tex;
+static int fb_tex_width, fb_tex_height;
+
+void slow_post(unsigned int sdr)
+{
+       update_fbtex();
+
+       glBindTexture(GL_TEXTURE_2D, fb_tex);
+       glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, win_width, win_height);
+
+       glPushAttrib(GL_ENABLE_BIT);
+       glDisable(GL_DEPTH_TEST);
+       glDisable(GL_LIGHTING);
+
+       glUseProgram(sdr);
+
+       float umax = (float)win_width / (float)fb_tex_width;
+       float vmax = (float)win_height / (float)fb_tex_height;
+
+       glBegin(GL_QUADS);
+       glTexCoord2f(0, 0);
+       glVertex2f(-1, -1);
+       glTexCoord2f(umax, 0);
+       glVertex2f(1, -1);
+       glTexCoord2f(umax, vmax);
+       glVertex2f(1, 1);
+       glTexCoord2f(0, vmax);
+       glVertex2f(-1, 1);
+       glEnd();
+
+       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) {
+               return;         // nothing to do
+       }
+       if(!fb_tex) {
+               glGenTextures(1, &fb_tex);
+               glBindTexture(GL_TEXTURE_2D, fb_tex);
+               glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+               glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+       } else {
+               glBindTexture(GL_TEXTURE_2D, fb_tex);
+       }
+
+       fb_tex_width = next_pow2(win_width);
+       fb_tex_height = next_pow2(win_height);
+       glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, fb_tex_width, fb_tex_height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+}
diff --git a/src/post.h b/src/post.h
new file mode 100644 (file)
index 0000000..737cb02
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef POST_H_
+#define POST_H_
+
+void slow_post(unsigned int sdr);
+
+#endif // POST_H_
diff --git a/src/scene.cc b/src/scene.cc
new file mode 100644 (file)
index 0000000..683c9ec
--- /dev/null
@@ -0,0 +1,254 @@
+#include <regex>
+#include "scene.h"
+#include "objmesh.h"
+#include "app.h"
+
+static void destroy_node_tree(SceneNode *n);
+
+Scene::Scene()
+{
+       metascn = 0;
+       nodes = 0;
+
+       walk_mesh = 0;
+}
+
+Scene::~Scene()
+{
+       destroy();
+}
+
+void Scene::destroy()
+{
+       destroy_node_tree(nodes);
+       nodes = 0;
+
+       for(int i=0; i<(int)meshes.size(); i++) {
+               delete meshes[i];
+       }
+       meshes.clear();
+
+       delete walk_mesh;
+       walk_mesh = 0;
+
+       for(int i=0; i<(int)objects.size(); i++) {
+               delete objects[i];
+       }
+       objects.clear();
+}
+
+static void destroy_node_tree(SceneNode *n)
+{
+       if(!n) return;
+
+       int nsub = n->get_num_children();
+       for(int i=0; i<nsub; i++) {
+               destroy_node_tree(n->get_child(i));
+       }
+       delete n;
+}
+
+// Scene::load defined in sceneload.cc
+
+bool Scene::merge(Scene *scn)
+{
+       if(walk_mesh) {
+               if(scn->walk_mesh) {
+                       walk_mesh->append(*scn->walk_mesh);
+                       delete scn->walk_mesh;
+                       scn->walk_mesh = 0;
+               }
+       } else {
+               walk_mesh = scn->walk_mesh;
+               scn->walk_mesh = 0;
+       }
+
+       int nmeshes = scn->meshes.size();
+       for(int i=0; i<nmeshes; i++) {
+               meshes.push_back(scn->meshes[i]);
+       }
+       scn->meshes.clear();
+
+       int nobj = scn->objects.size();
+       for(int i=0; i<nobj; i++) {
+               objects.push_back(scn->objects[i]);
+       }
+       scn->objects.clear();
+
+       if(nodes) {
+               int nchildren = scn->nodes ? scn->nodes->get_num_children() : 0;
+               for(int i=0; i<nchildren; i++) {
+                       SceneNode *n = scn->nodes->get_child(i);
+                       scn->nodes->remove_child(n);
+                       nodes->add_child(n);
+               }
+               if(scn->nodes && scn->nodes->get_num_objects() > 0) {
+                       warning_log("merging with scene which has objects in its root node. these objects will not be merged!\n");
+               }
+               delete scn->nodes;
+       } else {
+               nodes = scn->nodes;
+       }
+       scn->nodes = 0;
+
+       return true;
+}
+
+void Scene::add_object(Object *obj)
+{
+       objects.push_back(obj);
+}
+
+bool Scene::remove_object(Object *obj)
+{
+       std::vector<Object*>::iterator it = std::find(objects.begin(), objects.end(), obj);
+       if(it != objects.end()) {
+               objects.erase(it);
+               return true;
+       }
+       return false;
+}
+
+bool Scene::have_object(Object *obj) const
+{
+       return std::find(objects.begin(), objects.end(), obj) != objects.end();
+}
+
+void Scene::add_mesh(Mesh *m)
+{
+       meshes.push_back(m);
+}
+
+bool Scene::remove_mesh(Mesh *m)
+{
+       std::vector<Mesh*>::iterator it = std::find(meshes.begin(), meshes.end(), m);
+       if(it != meshes.end()) {
+               meshes.erase(it);
+               return true;
+       }
+       return false;
+}
+
+bool Scene::have_mesh(Mesh *m) const
+{
+       return std::find(meshes.begin(), meshes.end(), m) != meshes.end();
+}
+
+void Scene::add_node(SceneNode *n)
+{
+       // we always want to have a dedicated root node
+       if(!nodes) {
+               nodes = new SceneNode;
+               nodes->scene = this;
+               nodes->set_name("root");
+       }
+
+       nodes->add_child(n);
+}
+
+bool Scene::remove_node(SceneNode *n)
+{
+       SceneNode *par;
+       if(!n || !(par = n->get_parent())) {
+               return false;
+       }
+
+       int nsub = n->get_num_children();
+       for(int i=0; i<nsub; i++) {
+               SceneNode *c = n->get_child(i);
+               n->remove_child(c);
+               par->add_child(c);
+       }
+
+       return par->remove_child(n);
+}
+
+bool Scene::have_node(SceneNode *n) const
+{
+       return n->scene == this;
+}
+
+static void find_nodes_rec(std::list<SceneNode*> *res, SceneNode *tree, const std::regex &re)
+{
+       if(std::regex_match(tree->get_name(), re)) {
+               res->push_back(tree);
+       }
+
+       int num = tree->get_num_children();
+       for(int i=0; i<num; i++) {
+               find_nodes_rec(res, tree->get_child(i), re);
+       }
+}
+
+Scene *Scene::extract_nodes(const char *qstr)
+{
+       if(!nodes) return 0;
+
+       std::regex re{qstr};
+
+       std::list<SceneNode*> nodelist;
+       find_nodes_rec(&nodelist, nodes, re);
+       if(nodelist.empty()) {
+               return 0;
+       }
+
+       Scene *res = new Scene;
+
+       for(SceneNode *n : nodelist) {
+
+               int nobj = n->get_num_objects();
+               for(int i=0; i<nobj; i++) {
+                       Object *obj = n->get_object(i);
+                       if(obj->get_type() == OBJ_MESH) {
+                               // XXX this assumes that meshes aren't shared between objects.
+                               // maybe we'll have to refcount them at some point, and copy if nref>1
+                               ObjMesh *om = (ObjMesh*)obj;
+                               remove_mesh(om->mesh);
+                               res->add_mesh(om->mesh);
+                       }
+
+                       remove_object(obj);
+                       res->add_object(obj);
+               }
+
+               remove_node(n);
+               res->add_node(n);
+       }
+       return res;
+}
+
+void Scene::apply_xform()
+{
+       nodes->apply_xform();
+}
+
+void Scene::update(float dt)
+{
+       if(nodes) {
+               nodes->update(dt);
+       }
+
+       int nobj = objects.size();
+       for(int i=0; i<nobj; i++) {
+               if(!objects[i]->node) {
+                       // only update objects which don't belong to a scenegraph node
+                       // to avoid updating objects twice
+                       objects[i]->update(dt);
+               }
+       }
+}
+
+void Scene::draw() const
+{
+       if(!objects.empty()) {
+               int nobj = objects.size();
+               for(int i=0; i<nobj; i++) {
+                       objects[i]->draw();
+               }
+       } else {
+               int nmesh = meshes.size();
+               for(int i=0; i<nmesh; i++) {
+                       meshes[i]->draw();
+               }
+       }
+}
diff --git a/src/scene.h b/src/scene.h
new file mode 100644 (file)
index 0000000..1cf8dcc
--- /dev/null
@@ -0,0 +1,90 @@
+#ifndef SCENE_H_
+#define SCENE_H_
+
+#include <vector>
+#include <list>
+#include "mesh.h"
+#include "snode.h"
+#include "texture.h"
+#include "dataset.h"
+#include "datamap.h"
+
+enum {
+       SCNLOAD_FLIPYZ = 1,
+       SCNLOAD_FLIPTEX = 2,
+
+       SCNLOAD_STAGE_IO = 0x4000,
+       SCNLOAD_STAGE_GL = 0x8000
+};
+
+class MetaScene;
+
+class Scene {
+public:
+       MetaScene *metascn;
+       DataMap datamap;
+
+       // meshes objects and nodes are owned by Scene
+       std::vector<Mesh*> meshes;
+       std::vector<Object*> objects;
+       SceneNode *nodes;
+
+       Mesh *walk_mesh;
+
+       TextureSet *texset;     // only owned by Scene if own_texset is true
+       void *loader_data;
+
+       explicit Scene();
+       ~Scene();
+
+       Scene(const Scene &rhs) = delete;
+       Scene &operator =(const Scene &rhs) = delete;
+
+       void destroy();
+
+       bool load(const char *fname, unsigned int flags = 0);
+
+       // merge scn into this scene, leaving scn empty and safe to destroy
+       bool merge(Scene *scn);
+
+       void add_object(Object *obj);
+       bool remove_object(Object *obj);
+       bool have_object(Object *obj) const;
+
+       void add_mesh(Mesh *m);
+       bool remove_mesh(Mesh *m);
+       bool have_mesh(Mesh *m) const;
+
+       void add_node(SceneNode *n);
+       bool remove_node(SceneNode *n);
+       bool have_node(SceneNode *n) const;
+
+       /* find and remove all nodes whose names match the regexp
+        * XXX at the moment this has the effect of flattening the hierarchy in the
+        * result scene. If we ever need verbatim extraction of whole subtrees we'll
+        * need to change the algorithm.
+        */
+       Scene *extract_nodes(const char *qstr);
+
+       /* bake all node transformations to their respective meshes and make the
+        * node transformations identity.
+        * XXX this assumes no mesh is shared by two nodes.
+        */
+       void apply_xform();
+
+       void update(float dt);
+       void draw() const;
+};
+
+class SceneSet : public DataSet<Scene*> {
+private:
+       static Scene *create_scene();
+       static bool load_scene(Scene *scn, const char *fname);
+       static bool done_scene(Scene *scn);
+       static void free_scene(Scene *scn);
+
+public:
+       SceneSet();
+};
+
+#endif // SCENE_H_
diff --git a/src/sceneload.cc b/src/sceneload.cc
new file mode 100644 (file)
index 0000000..e33e457
--- /dev/null
@@ -0,0 +1,395 @@
+#include <stdio.h>
+#include <assert.h>
+#include <string>
+#include <vector>
+#include <map>
+#include <gmath/gmath.h>
+#include <assimp/cimport.h>
+#include <assimp/postprocess.h>
+#include <assimp/scene.h>
+#include <assimp/mesh.h>
+#include <assimp/material.h>
+#include <assimp/anim.h>
+#include <assimp/vector3.h>
+#include <assimp/matrix4x4.h>
+#include <assimp/quaternion.h>
+#include "app.h"
+#include "scene.h"
+#include "objmesh.h"
+#include "datamap.h"
+#include "logger.h"
+#include "metascene.h"
+
+static bool load_material(Scene *scn, Material *mat, const aiMaterial *aimat);
+static SceneNode *load_node(Scene *scn, const aiScene *aiscn, unsigned int flags, const aiNode *ainode);
+static Mesh *load_mesh(Scene *scn, const aiScene *aiscn, unsigned int flags, const aiMesh *aimesh);
+/*static const char *mprop_semantic(int x);
+static int count_textures(const aiMaterial *aimat);*/
+static int assimp_textype(aiTextureType type);
+//static const char *assimp_textypestr(aiTextureType type);
+
+static Mat4 assimp_matrix(const aiMatrix4x4 &aim);
+
+/*static Vec3 assimp_vector(const aiVector3D &v);
+static Quat assimp_quat(const aiQuaternion &q);
+static long assimp_time(const aiAnimation *anim, double aitime);
+static void print_hierarchy(const aiNode *node);
+*/
+
+struct LoaderData {
+       const aiScene *aiscn;
+       std::string fname;
+       std::map<std::string, SceneNode*> node_by_name;
+       std::map<aiMesh*, Mesh*> mesh_by_aimesh;
+};
+
+#define LD_STAGE_MASK  0xf000
+
+bool Scene::load(const char *fname, unsigned int flags)
+{
+       if((flags & LD_STAGE_MASK) == 0) {
+               // not passing either of the stage specifiers, means do the whole job
+               flags |= LD_STAGE_MASK;
+       }
+
+       // first perform I/O and all operations not requiring access to an OpenGL context
+       if(flags & SCNLOAD_STAGE_IO) {
+               unsigned int ppflags = aiProcess_CalcTangentSpace |
+                       aiProcess_GenNormals |
+                       aiProcess_JoinIdenticalVertices |
+                       aiProcess_Triangulate |
+                       aiProcess_SortByPType |
+                       aiProcess_GenUVCoords |
+                       //aiProcess_PreTransformVertices |
+                       aiProcess_TransformUVCoords;
+
+               if(flags & SCNLOAD_FLIPTEX) {
+                       ppflags |= aiProcess_FlipUVs;
+               }
+
+               info_log("Loading scene file: %s\n", fname);
+
+               const aiScene *aiscn = aiImportFile(fname, ppflags);
+               if(!aiscn) {
+                       error_log("failed to load scene file: %s\n", fname);
+                       return false;
+               }
+
+               // assimp adds its own root node, which might have transformations
+               Vec3 root_pos, root_scaling(1.0, 1.0, 1.0);
+               Quat root_rot;
+
+               if(aiscn->mRootNode) {
+                       Mat4 root_matrix = assimp_matrix(aiscn->mRootNode->mTransformation);
+                       root_pos = root_matrix.get_translation();
+                       root_rot = root_matrix.get_rotation();
+                       root_scaling = root_matrix.get_scaling();
+               }
+
+               if(!nodes) {
+                       nodes = new SceneNode;
+                       nodes->scene = this;
+                       nodes->set_name("root");
+                       nodes->set_position(root_pos);
+                       nodes->set_rotation(root_rot);
+                       nodes->set_scaling(root_scaling);
+               }
+
+               LoaderData *ldata = new LoaderData;
+               ldata->aiscn = aiscn;
+               ldata->fname = std::string(fname);
+               loader_data = (void*)ldata;
+       }
+
+       /* then, assuming we have successfully loaded everything, proceed to construct
+        * all the engine objects, which require access to the OpenGL context
+        */
+       if(flags & SCNLOAD_STAGE_GL) {
+               if(!loader_data) {
+                       error_log("second stage scene loader failed to find valid I/O data\n");
+                       return false;
+               }
+
+               LoaderData *ldata = (LoaderData*)loader_data;
+               const aiScene *aiscn = ldata->aiscn;
+               fname = ldata->fname.c_str();
+
+               // load all meshes
+               for(unsigned int i=0; i<aiscn->mNumMeshes; i++) {
+                       aiMesh *aimesh = aiscn->mMeshes[i];
+                       Mesh *mesh;
+
+                       switch(aimesh->mPrimitiveTypes) {
+                       case aiPrimitiveType_TRIANGLE:
+                               if((mesh = load_mesh(this, aiscn, flags, aimesh))) {
+                                       ldata->mesh_by_aimesh[aimesh] = mesh;
+                                       meshes.push_back(mesh);
+                               }
+                               break;
+
+                       default:
+                               error_log("unsupported primitive type: %u\n", aimesh->mPrimitiveTypes);
+                               break;
+                       }
+               }
+
+               // load all the nodes recursively
+               for(unsigned int i=0; i<aiscn->mRootNode->mNumChildren; i++) {
+                       SceneNode *node = load_node(this, aiscn, flags, aiscn->mRootNode->mChildren[i]);
+                       if(node) {
+                               nodes->add_child(node);
+                       }
+               }
+
+               info_log("loaded scene file: %s, %d meshes\n", fname, (int)meshes.size());
+
+               aiReleaseImport(aiscn);
+               delete ldata;
+               loader_data = 0;
+               nodes->update(0);
+       }
+       return true;
+}
+
+static bool load_material(Scene *scn, Material *mat, const aiMaterial *aimat)
+{
+       aiString name;
+       aiColor4D aicol;
+       float shin, shin_str;
+
+       if(aiGetMaterialString(aimat, AI_MATKEY_NAME, &name) == 0) {
+               mat->name = name.data;
+       } else {
+               mat->name = "unknown";
+       }
+       //info_log("load_material: %s\n", mat->name.c_str());
+
+       if(aiGetMaterialColor(aimat, AI_MATKEY_COLOR_DIFFUSE, &aicol) == 0) {
+               mat->diffuse = Vec3(aicol[0], aicol[1], aicol[2]);
+       }
+       if(aiGetMaterialColor(aimat, AI_MATKEY_COLOR_SPECULAR, &aicol) == 0) {
+               mat->specular = Vec3(aicol[0], aicol[1], aicol[2]);
+       }
+
+       unsigned int count = 1;
+       if(aiGetMaterialFloatArray(aimat, AI_MATKEY_SHININESS_STRENGTH, &shin_str, &count) != 0) {
+               shin_str = 1.0;
+       }
+       if(aiGetMaterialFloatArray(aimat, AI_MATKEY_SHININESS, &shin, &count) == 0) {
+               // XXX can't remember how I came up with this...
+               mat->shininess = shin * shin_str * 0.0001 * 128.0;
+       }
+
+       // load textures
+
+       const int num_tex_types = aiTextureType_UNKNOWN + 1;
+       for(int i=0; i<num_tex_types; i++) {
+               aiTextureType aitype = (aiTextureType)i;
+               int count = aiGetMaterialTextureCount(aimat, aitype);
+
+               for(int j=0; j<count; j++) {
+                       aiString aipath;
+                       if(aiGetMaterialTexture(aimat, aitype, j, &aipath) != 0) {
+                               continue;
+                       }
+
+                       char *fname = (char*)alloca(strlen(aipath.data) + 1);
+                       char *dptr = fname;
+                       char *sptr = aipath.data;
+                       do {
+                               *dptr++ = *sptr == '\\' ? '/' : *sptr;
+                       } while(*sptr++);
+
+                       int textype = assimp_textype(aitype);
+
+                       Texture *tex = texman.get_texture(fname, TEX_2D, &scn->datamap);
+                       assert(tex);
+                       mat->textures.push_back(tex);
+
+                       if(textype != MTL_TEX_UNKNOWN && !mat->stdtex[textype]) {
+                               mat->stdtex[textype] = tex;
+                       }
+               }
+       }
+
+       return true;
+}
+
+static SceneNode *load_node(Scene *scn, const aiScene *aiscn, unsigned int flags, const aiNode *ainode)
+{
+       LoaderData *ldata = (LoaderData*)scn->loader_data;
+
+       SceneNode *node = new SceneNode;
+       node->set_name(ainode->mName.data);
+
+       // transformation
+       Mat4 matrix = assimp_matrix(ainode->mTransformation);
+       Vec3 pos = matrix.get_translation();
+       Quat rot = matrix.get_rotation();
+       Vec3 scale = matrix.get_scaling();
+
+       node->set_position(pos);
+       node->set_rotation(rot);
+       node->set_scaling(scale);
+       node->dbg_xform = matrix;
+
+       // meshes
+       for(unsigned int i=0; i<ainode->mNumMeshes; i++) {
+               aiMesh *aimesh = aiscn->mMeshes[ainode->mMeshes[i]];
+
+               Mesh *mesh = ldata->mesh_by_aimesh[aimesh];
+               if(mesh) {
+                       ObjMesh *obj = new ObjMesh;
+                       obj->set_name(mesh->get_name());
+                       obj->mesh = mesh;
+                       // also grab the material of this mesh
+                       load_material(scn, &obj->mtl, aiscn->mMaterials[aimesh->mMaterialIndex]);
+
+                       node->add_object(obj);
+                       scn->objects.push_back(obj);
+               }
+       }
+
+       /* recurse to all children */
+       for(unsigned int i=0; i<ainode->mNumChildren; i++) {
+               SceneNode *child = load_node(scn, aiscn, flags, ainode->mChildren[i]);
+               if(child) {
+                       node->add_child(child);
+               }
+       }
+
+       ldata->node_by_name[node->get_name()] = node;
+       return node;
+}
+
+static Mesh *load_mesh(Scene *scn, const aiScene *aiscn, unsigned int flags, const aiMesh *aimesh)
+{
+       Mesh *mesh = new Mesh;
+       mesh->set_name(aimesh->mName.data);
+
+       int num_verts = aimesh->mNumVertices;
+       int num_faces = aimesh->mNumFaces;
+
+       mesh->set_attrib_data(MESH_ATTR_VERTEX, 3, num_verts, (float*)aimesh->mVertices);
+
+       if(aimesh->mNormals) {
+               mesh->set_attrib_data(MESH_ATTR_NORMAL, 3, num_verts, (float*)aimesh->mNormals);
+       }
+       if(aimesh->mTangents) {
+               mesh->set_attrib_data(MESH_ATTR_TANGENT, 3, num_verts, (float*)aimesh->mTangents);
+       }
+       if(aimesh->mTextureCoords[0]) {
+               mesh->set_attrib_data(MESH_ATTR_TEXCOORD, 3, num_verts, (float*)aimesh->mTextureCoords[0]);
+       }
+       if(aimesh->mTextureCoords[1]) {
+               mesh->set_attrib_data(MESH_ATTR_TEXCOORD2, 3, num_verts, (float*)aimesh->mTextureCoords[1]);
+       }
+
+       if(flags & SCNLOAD_FLIPYZ) {
+               Vec3 *vptr = (Vec3*)mesh->get_attrib_data(MESH_ATTR_VERTEX);
+               for(int i=0; i<num_verts; i++) {
+                       *vptr = vptr->xzy();
+                       ++vptr;
+               }
+
+               Vec3 *nptr = (Vec3*)mesh->get_attrib_data(MESH_ATTR_NORMAL);
+               for(int i=0; i<num_verts; i++) {
+                       *nptr = nptr->xzy();
+                       ++nptr;
+               }
+
+               Vec3 *tptr = (Vec3*)mesh->get_attrib_data(MESH_ATTR_TANGENT);
+               for(int i=0; i<num_verts; i++) {
+                       *tptr = tptr->xzy();
+                       ++tptr;
+               }
+       }
+
+       unsigned int *iptr = mesh->set_index_data(num_faces * 3);
+       for(int i=0; i<num_faces; i++) {
+               iptr[0] = aimesh->mFaces[i].mIndices[0];
+               iptr[1] = aimesh->mFaces[i].mIndices[flags & SCNLOAD_FLIPYZ ? 2 : 1];
+               iptr[2] = aimesh->mFaces[i].mIndices[flags & SCNLOAD_FLIPYZ ? 1 : 2];
+               iptr += 3;
+       }
+       return mesh;
+}
+
+static int assimp_textype(aiTextureType type)
+{
+       switch(type) {
+       case aiTextureType_DIFFUSE:
+               return MTL_TEX_DIFFUSE;
+       case aiTextureType_SPECULAR:
+               return MTL_TEX_SPECULAR;
+       case aiTextureType_NORMALS:
+               return MTL_TEX_NORMALMAP;
+       case aiTextureType_LIGHTMAP:
+       case aiTextureType_EMISSIVE:
+               return MTL_TEX_LIGHTMAP;
+       case aiTextureType_REFLECTION:
+               return MTL_TEX_ENVMAP;
+       default:
+               break;
+       }
+       return MTL_TEX_UNKNOWN;
+}
+
+/*static const char *assimp_textypestr(aiTextureType type)
+{
+       switch(type) {
+       case aiTextureType_DIFFUSE:
+               return "diffuse";
+       case aiTextureType_SPECULAR:
+               return "specular";
+       case aiTextureType_NORMALS:
+               return "normalmap";
+       case aiTextureType_LIGHTMAP:
+       case aiTextureType_EMISSIVE:
+               return "lightmap";
+       case aiTextureType_REFLECTION:
+               return "envmap";
+       default:
+               break;
+       }
+       return "unknown";
+}*/
+
+static Mat4 assimp_matrix(const aiMatrix4x4 &aim)
+{
+       Mat4 m;
+       memcpy(m[0], &aim, 16 * sizeof(float));
+       return transpose(m);
+}
+
+
+// --- SceneSet ---
+
+SceneSet::SceneSet()
+       : DataSet<Scene*>(create_scene, load_scene, done_scene, free_scene)
+{
+}
+
+Scene *SceneSet::create_scene()
+{
+       return new Scene;
+}
+
+bool SceneSet::load_scene(Scene *scn, const char *fname)
+{
+       return scn->load(fname, SCNLOAD_FLIPTEX | SCNLOAD_STAGE_IO);
+}
+
+bool SceneSet::done_scene(Scene *scn)
+{
+       bool res = scn->load(0, SCNLOAD_STAGE_GL);
+       if(scn->metascn) {
+               scn->metascn->scene_loaded(scn);
+       }
+       return res;
+}
+
+void SceneSet::free_scene(Scene *scn)
+{
+       delete scn;
+}
diff --git a/src/sceneload.h b/src/sceneload.h
new file mode 100644 (file)
index 0000000..2af093d
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef SCENELOAD_H_
+#define SCENELOAD_H_
+
+#include "scene.h"
+
+bool load_scene(
+
+#endif // SCENELOAD_H_
diff --git a/src/sdr.c b/src/sdr.c
new file mode 100644 (file)
index 0000000..6b06991
--- /dev/null
+++ b/src/sdr.c
@@ -0,0 +1,563 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <assert.h>
+#include "opengl.h"
+#include "logger.h"
+
+#if defined(unix) || defined(__unix__)
+#include <unistd.h>
+#include <sys/stat.h>
+#endif /* unix */
+
+#include "sdr.h"
+
+static const char *sdrtypestr(unsigned int sdrtype);
+static int sdrtypeidx(unsigned int sdrtype);
+
+
+unsigned int create_vertex_shader(const char *src)
+{
+       return create_shader(src, GL_VERTEX_SHADER);
+}
+
+unsigned int create_pixel_shader(const char *src)
+{
+       return create_shader(src, GL_FRAGMENT_SHADER);
+}
+
+unsigned int create_tessctl_shader(const char *src)
+{
+#ifdef GL_TESS_CONTROL_SHADER
+       return create_shader(src, GL_TESS_CONTROL_SHADER);
+#else
+       return 0;
+#endif
+}
+
+unsigned int create_tesseval_shader(const char *src)
+{
+#ifdef GL_TESS_EVALUATION_SHADER
+       return create_shader(src, GL_TESS_EVALUATION_SHADER);
+#else
+       return 0;
+#endif
+}
+
+unsigned int create_geometry_shader(const char *src)
+{
+#ifdef GL_GEOMETRY_SHADER
+       return create_shader(src, GL_GEOMETRY_SHADER);
+#else
+       return 0;
+#endif
+}
+
+unsigned int create_shader(const char *src, unsigned int sdr_type)
+{
+       unsigned int sdr;
+       int success, info_len;
+       char *info_str = 0;
+       const char *src_str[3], *header, *footer;
+       int src_str_count = 0;
+       GLenum err;
+
+       if((header = get_shader_header(sdr_type))) {
+               src_str[src_str_count++] = header;
+       }
+       src_str[src_str_count++] = src;
+       if((footer = get_shader_footer(sdr_type))) {
+               src_str[src_str_count++] = footer;
+       }
+
+       sdr = glCreateShader(sdr_type);
+       assert(glGetError() == GL_NO_ERROR);
+       glShaderSource(sdr, src_str_count, src_str, 0);
+       err = glGetError();
+       assert(err == GL_NO_ERROR);
+       glCompileShader(sdr);
+       assert(glGetError() == GL_NO_ERROR);
+
+       glGetShaderiv(sdr, GL_COMPILE_STATUS, &success);
+       assert(glGetError() == GL_NO_ERROR);
+       glGetShaderiv(sdr, GL_INFO_LOG_LENGTH, &info_len);
+       assert(glGetError() == GL_NO_ERROR);
+
+       if(info_len) {
+               if((info_str = malloc(info_len + 1))) {
+                       glGetShaderInfoLog(sdr, info_len, 0, info_str);
+                       assert(glGetError() == GL_NO_ERROR);
+                       info_str[info_len] = 0;
+               }
+       }
+
+       if(success) {
+               info_log(info_str ? "done: %s\n" : "done\n", info_str);
+       } else {
+               error_log(info_str ? "failed: %s\n" : "failed\n", info_str);
+               glDeleteShader(sdr);
+               sdr = 0;
+       }
+
+       free(info_str);
+       return sdr;
+}
+
+void free_shader(unsigned int sdr)
+{
+       glDeleteShader(sdr);
+}
+
+unsigned int load_vertex_shader(const char *fname)
+{
+       return load_shader(fname, GL_VERTEX_SHADER);
+}
+
+unsigned int load_pixel_shader(const char *fname)
+{
+       return load_shader(fname, GL_FRAGMENT_SHADER);
+}
+
+unsigned int load_tessctl_shader(const char *fname)
+{
+#ifdef GL_TESS_CONTROL_SHADER
+       return load_shader(fname, GL_TESS_CONTROL_SHADER);
+#else
+       return 0;
+#endif
+}
+
+unsigned int load_tesseval_shader(const char *fname)
+{
+#ifdef GL_TESS_EVALUATION_SHADER
+       return load_shader(fname, GL_TESS_EVALUATION_SHADER);
+#else
+       return 0;
+#endif
+}
+
+unsigned int load_geometry_shader(const char *fname)
+{
+#ifdef GL_GEOMETRY_SHADER
+       return load_shader(fname, GL_GEOMETRY_SHADER);
+#else
+       return 0;
+#endif
+}
+
+unsigned int load_shader(const char *fname, unsigned int sdr_type)
+{
+       unsigned int sdr;
+       size_t filesize;
+       FILE *fp;
+       char *src;
+
+       if(!(fp = fopen(fname, "rb"))) {
+               error_log("failed to open shader %s: %s\n", fname, strerror(errno));
+               return 0;
+       }
+
+       fseek(fp, 0, SEEK_END);
+       filesize = ftell(fp);
+       fseek(fp, 0, SEEK_SET);
+
+       if(!(src = malloc(filesize + 1))) {
+               fclose(fp);
+               return 0;
+       }
+       fread(src, 1, filesize, fp);
+       src[filesize] = 0;
+       fclose(fp);
+
+       info_log("compiling %s shader: %s... ", sdrtypestr(sdr_type), fname);
+       sdr = create_shader(src, sdr_type);
+
+       free(src);
+       return sdr;
+}
+
+
+/* ---- gpu programs ---- */
+
+unsigned int create_program(void)
+{
+       unsigned int prog = glCreateProgram();
+       assert(glGetError() == GL_NO_ERROR);
+       return prog;
+}
+
+unsigned int create_program_link(unsigned int sdr0, ...)
+{
+       unsigned int prog, sdr;
+       va_list ap;
+
+       if(!(prog = create_program())) {
+               return 0;
+       }
+
+       attach_shader(prog, sdr0);
+       if(glGetError()) {
+               return 0;
+       }
+
+       va_start(ap, sdr0);
+       while((sdr = va_arg(ap, unsigned int))) {
+               attach_shader(prog, sdr);
+               if(glGetError()) {
+                       return 0;
+               }
+       }
+       va_end(ap);
+
+       if(link_program(prog) == -1) {
+               free_program(prog);
+               return 0;
+       }
+       return prog;
+}
+
+unsigned int create_program_load(const char *vfile, const char *pfile)
+{
+       unsigned int vs = 0, ps = 0;
+
+       if(vfile && *vfile && !(vs = load_vertex_shader(vfile))) {
+               return 0;
+       }
+       if(pfile && *pfile && !(ps = load_pixel_shader(pfile))) {
+               return 0;
+       }
+       return create_program_link(vs, ps, 0);
+}
+
+void free_program(unsigned int sdr)
+{
+       glDeleteProgram(sdr);
+}
+
+void attach_shader(unsigned int prog, unsigned int sdr)
+{
+       int err;
+
+       if(prog && sdr) {
+               assert(glGetError() == GL_NO_ERROR);
+               glAttachShader(prog, sdr);
+               if((err = glGetError()) != GL_NO_ERROR) {
+                       error_log("failed to attach shader %u to program %u (err: 0x%x)\n", sdr, prog, err);
+                       abort();
+               }
+       }
+}
+
+int link_program(unsigned int prog)
+{
+       int linked, info_len, retval = 0;
+       char *info_str = 0;
+
+       glLinkProgram(prog);
+       assert(glGetError() == GL_NO_ERROR);
+       glGetProgramiv(prog, GL_LINK_STATUS, &linked);
+       assert(glGetError() == GL_NO_ERROR);
+       glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &info_len);
+       assert(glGetError() == GL_NO_ERROR);
+
+       if(info_len) {
+               if((info_str = malloc(info_len + 1))) {
+                       glGetProgramInfoLog(prog, info_len, 0, info_str);
+                       assert(glGetError() == GL_NO_ERROR);
+                       info_str[info_len] = 0;
+               }
+       }
+
+       if(linked) {
+               info_log(info_str ? "linking done: %s\n" : "linking done\n", info_str);
+       } else {
+               error_log(info_str ? "linking failed: %s\n" : "linking failed\n", info_str);
+               retval = -1;
+       }
+
+       free(info_str);
+       return retval;
+}
+
+int bind_program(unsigned int prog)
+{
+       GLenum err;
+
+       glUseProgram(prog);
+       if(prog && (err = glGetError()) != GL_NO_ERROR) {
+               /* maybe the program is not linked, try linking first */
+               if(err == GL_INVALID_OPERATION) {
+                       if(link_program(prog) == -1) {
+                               return -1;
+                       }
+                       glUseProgram(prog);
+                       return glGetError() == GL_NO_ERROR ? 0 : -1;
+               }
+               return -1;
+       }
+       return 0;
+}
+
+/* ugly but I'm not going to write the same bloody code over and over */
+#define BEGIN_UNIFORM_CODE \
+       int loc, curr_prog; \
+       glGetIntegerv(GL_CURRENT_PROGRAM, &curr_prog); \
+       if((unsigned int)curr_prog != prog && bind_program(prog) == -1) { \
+               return -1; \
+       } \
+       if((loc = glGetUniformLocation(prog, name)) != -1)
+
+#define END_UNIFORM_CODE \
+       if((unsigned int)curr_prog != prog) { \
+               bind_program(curr_prog); \
+       } \
+       return loc == -1 ? -1 : 0
+
+int get_uniform_loc(unsigned int prog, const char *name)
+{
+       int loc, curr_prog;
+       glGetIntegerv(GL_CURRENT_PROGRAM, &curr_prog);
+       if((unsigned int)curr_prog != prog && bind_program(prog) == -1) {
+               return -1;
+       }
+       loc = glGetUniformLocation(prog, name);
+       if((unsigned int)curr_prog != prog) {
+               bind_program(curr_prog);
+       }
+       return loc;
+}
+
+int set_uniform_int(unsigned int prog, const char *name, int val)
+{
+       BEGIN_UNIFORM_CODE {
+               glUniform1i(loc, val);
+       }
+       END_UNIFORM_CODE;
+}
+
+int set_uniform_float(unsigned int prog, const char *name, float val)
+{
+       BEGIN_UNIFORM_CODE {
+               glUniform1f(loc, val);
+       }
+       END_UNIFORM_CODE;
+}
+
+int set_uniform_float2(unsigned int prog, const char *name, float x, float y)
+{
+       BEGIN_UNIFORM_CODE {
+               glUniform2f(loc, x, y);
+       }
+       END_UNIFORM_CODE;
+}
+
+int set_uniform_float3(unsigned int prog, const char *name, float x, float y, float z)
+{
+       BEGIN_UNIFORM_CODE {
+               glUniform3f(loc, x, y, z);
+       }
+       END_UNIFORM_CODE;
+}
+
+int set_uniform_float4(unsigned int prog, const char *name, float x, float y, float z, float w)
+{
+       BEGIN_UNIFORM_CODE {
+               glUniform4f(loc, x, y, z, w);
+       }
+       END_UNIFORM_CODE;
+}
+
+int set_uniform_matrix4(unsigned int prog, const char *name, const float *mat)
+{
+       BEGIN_UNIFORM_CODE {
+               glUniformMatrix4fv(loc, 1, GL_FALSE, mat);
+       }
+       END_UNIFORM_CODE;
+}
+
+int set_uniform_matrix4_transposed(unsigned int prog, const char *name, const float *mat)
+{
+       BEGIN_UNIFORM_CODE {
+               glUniformMatrix4fv(loc, 1, GL_TRUE, mat);
+       }
+       END_UNIFORM_CODE;
+}
+
+int get_attrib_loc(unsigned int prog, const char *name)
+{
+       int loc, curr_prog;
+
+       glGetIntegerv(GL_CURRENT_PROGRAM, &curr_prog);
+       if((unsigned int)curr_prog != prog && bind_program(prog) == -1) {
+               return -1;
+       }
+
+       loc = glGetAttribLocation(prog, (char*)name);
+
+       if((unsigned int)curr_prog != prog) {
+               bind_program(curr_prog);
+       }
+       return loc;
+}
+
+void set_attrib_float3(int attr_loc, float x, float y, float z)
+{
+       glVertexAttrib3f(attr_loc, x, y, z);
+}
+
+/* ---- shader composition ---- */
+struct string {
+       char *text;
+       int len;
+};
+
+#define NUM_SHADER_TYPES       5
+static struct string header[NUM_SHADER_TYPES];
+static struct string footer[NUM_SHADER_TYPES];
+
+static void clear_string(struct string *str)
+{
+       free(str->text);
+       str->text = 0;
+       str->len = 0;
+}
+
+static void append_string(struct string *str, const char *s)
+{
+       int len, newlen;
+       char *newstr;
+
+       if(!s || !*s) return;
+
+       len = strlen(s);
+       newlen = str->len + len;
+       if(!(newstr = malloc(newlen + 2))) {    /* leave space for a possible newline */
+               error_log("shader composition: failed to append string of size %d\n", len);
+               abort();
+       }
+
+       if(str->text) {
+               memcpy(newstr, str->text, str->len);
+       }
+       memcpy(newstr + str->len, s, len + 1);
+
+       if(s[len - 1] != '\n') {
+               newstr[newlen] = '\n';
+               newstr[newlen + 1] = 0;
+       }
+
+       free(str->text);
+       str->text = newstr;
+       str->len = newlen;
+}
+
+void clear_shader_header(unsigned int type)
+{
+       if(type) {
+               int idx = sdrtypeidx(type);
+               clear_string(&header[idx]);
+       } else {
+               int i;
+               for(i=0; i<NUM_SHADER_TYPES; i++) {
+                       clear_string(&header[i]);
+               }
+       }
+}
+
+void clear_shader_footer(unsigned int type)
+{
+       if(type) {
+               int idx = sdrtypeidx(type);
+               clear_string(&footer[idx]);
+       } else {
+               int i;
+               for(i=0; i<NUM_SHADER_TYPES; i++) {
+                       clear_string(&footer[i]);
+               }
+       }
+}
+
+void add_shader_header(unsigned int type, const char *s)
+{
+       if(type) {
+               int idx = sdrtypeidx(type);
+               append_string(&header[idx], s);
+       } else {
+               int i;
+               for(i=0; i<NUM_SHADER_TYPES; i++) {
+                       append_string(&header[i], s);
+               }
+       }
+}
+
+void add_shader_footer(unsigned int type, const char *s)
+{
+       if(type) {
+               int idx = sdrtypeidx(type);
+               append_string(&footer[idx], s);
+       } else {
+               int i;
+               for(i=0; i<NUM_SHADER_TYPES; i++) {
+                       append_string(&footer[i], s);
+               }
+       }
+}
+
+const char *get_shader_header(unsigned int type)
+{
+       int idx = sdrtypeidx(type);
+       return header[idx].text;
+}
+
+const char *get_shader_footer(unsigned int type)
+{
+       int idx = sdrtypeidx(type);
+       return footer[idx].text;
+}
+
+static const char *sdrtypestr(unsigned int sdrtype)
+{
+       switch(sdrtype) {
+       case GL_VERTEX_SHADER:
+               return "vertex";
+       case GL_FRAGMENT_SHADER:
+               return "pixel";
+#ifdef GL_TESS_CONTROL_SHADER
+       case GL_TESS_CONTROL_SHADER:
+               return "tessellation control";
+#endif
+#ifdef GL_TESS_EVALUATION_SHADER
+       case GL_TESS_EVALUATION_SHADER:
+               return "tessellation evaluation";
+#endif
+#ifdef GL_GEOMETRY_SHADER
+       case GL_GEOMETRY_SHADER:
+               return "geometry";
+#endif
+
+       default:
+               break;
+       }
+       return "<unknown>";
+}
+
+static int sdrtypeidx(unsigned int sdrtype)
+{
+       switch(sdrtype) {
+       case GL_VERTEX_SHADER:
+               return 0;
+       case GL_FRAGMENT_SHADER:
+               return 1;
+       case GL_TESS_CONTROL_SHADER:
+               return 2;
+       case GL_TESS_EVALUATION_SHADER:
+               return 3;
+       case GL_GEOMETRY_SHADER:
+               return 4;
+       default:
+               break;
+       }
+       return 0;
+}
diff --git a/src/sdr.h b/src/sdr.h
new file mode 100644 (file)
index 0000000..7bf2389
--- /dev/null
+++ b/src/sdr.h
@@ -0,0 +1,68 @@
+#ifndef SDR_H_
+#define SDR_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* ---- shaders ---- */
+unsigned int create_vertex_shader(const char *src);
+unsigned int create_pixel_shader(const char *src);
+unsigned int create_tessctl_shader(const char *src);
+unsigned int create_tesseval_shader(const char *src);
+unsigned int create_geometry_shader(const char *src);
+unsigned int create_shader(const char *src, unsigned int sdr_type);
+void free_shader(unsigned int sdr);
+
+unsigned int load_vertex_shader(const char *fname);
+unsigned int load_pixel_shader(const char *fname);
+unsigned int load_tessctl_shader(const char *fname);
+unsigned int load_tesseval_shader(const char *fname);
+unsigned int load_geometry_shader(const char *fname);
+unsigned int load_shader(const char *src, unsigned int sdr_type);
+
+int add_shader(const char *fname, unsigned int sdr);
+int remove_shader(const char *fname);
+
+/* ---- gpu programs ---- */
+unsigned int create_program(void);
+unsigned int create_program_link(unsigned int sdr0, ...);
+unsigned int create_program_load(const char *vfile, const char *pfile);
+void free_program(unsigned int sdr);
+
+void attach_shader(unsigned int prog, unsigned int sdr);
+int link_program(unsigned int prog);
+int bind_program(unsigned int prog);
+
+int get_uniform_loc(unsigned int prog, const char *name);
+
+int set_uniform_int(unsigned int prog, const char *name, int val);
+int set_uniform_float(unsigned int prog, const char *name, float val);
+int set_uniform_float2(unsigned int prog, const char *name, float x, float y);
+int set_uniform_float3(unsigned int prog, const char *name, float x, float y, float z);
+int set_uniform_float4(unsigned int prog, const char *name, float x, float y, float z, float w);
+int set_uniform_matrix4(unsigned int prog, const char *name, const float *mat);
+int set_uniform_matrix4_transposed(unsigned int prog, const char *name, const float *mat);
+
+int get_attrib_loc(unsigned int prog, const char *name);
+void set_attrib_float3(int attr_loc, float x, float y, float z);
+
+/* ---- shader composition ---- */
+
+/* clear shader header/footer text.
+ * pass the shader type to clear, or 0 to clear all types */
+void clear_shader_header(unsigned int type);
+void clear_shader_footer(unsigned int type);
+/* append text to the header/footer of a specific shader type
+ * or use type 0 to add it to all shade types */
+void add_shader_header(unsigned int type, const char *s);
+void add_shader_footer(unsigned int type, const char *s);
+/* get the current header/footer text for a specific shader type */
+const char *get_shader_header(unsigned int type);
+const char *get_shader_footer(unsigned int type);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SDR_H_ */
diff --git a/src/shadow.cc b/src/shadow.cc
new file mode 100644 (file)
index 0000000..357ed82
--- /dev/null
@@ -0,0 +1,147 @@
+#define GPH_NAMESPACE
+#include <assert.h>
+#include "opengl.h"
+#include "shadow.h"
+#include "vmath/vmath.h"
+
+
+bool shadow_pass;
+
+static int tex_sz, prev_vp[4];
+static unsigned int fbo, depth_tex, rb_color;
+static gph::Mat4 shadow_mat;
+
+bool init_shadow(int sz)
+{
+       tex_sz = sz;
+       printf("initializing shadow buffer (%dx%d)\n", tex_sz, tex_sz);
+
+       glGenFramebuffers(1, &fbo);
+       glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+       const float border_color[] = {1, 1, 1, 1};
+
+       glGenTextures(1, &depth_tex);
+       glBindTexture(GL_TEXTURE_2D, depth_tex);
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
+       glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
+       glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, tex_sz, tex_sz, 0,
+                       GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
+       glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_tex, 0);
+
+       assert(glGetError() == GL_NO_ERROR);
+
+       glDrawBuffer(GL_FALSE);
+       glReadBuffer(GL_FALSE);
+
+       if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+               fprintf(stderr, "incomplete framebuffer\n");
+               return false;
+       }
+
+       glBindFramebuffer(GL_FRAMEBUFFER, 0);
+       glDrawBuffer(GL_BACK);
+       glReadBuffer(GL_BACK);
+       assert(glGetError() == GL_NO_ERROR);
+
+       return true;
+}
+
+void destroy_shadow()
+{
+       glDeleteTextures(1, &depth_tex);
+       glDeleteRenderbuffers(1, &rb_color);
+       glDeleteFramebuffers(1, &fbo);
+}
+
+void begin_shadow_pass(const gph::Vec3 &lpos, const gph::Vec3 &ltarg, float lfov, float znear, float zfar)
+{
+       shadow_pass = true;
+
+       glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT);
+       glDisable(GL_LIGHTING);
+       glColorMask(0, 0, 0, 0);
+       glDepthMask(1);
+
+       Matrix4x4 viewmat;
+       glGetFloatv(GL_MODELVIEW_MATRIX, viewmat[0]);
+       viewmat.transpose();
+
+       Matrix4x4 lt_viewmat, lt_projmat;
+       lt_projmat.set_perspective(DEG_TO_RAD(lfov) * 2.0, 1.0, znear, zfar);
+       lt_viewmat.set_lookat(Vector3(lpos.x, lpos.y, lpos.z), Vector3(ltarg.x, ltarg.y, ltarg.z), Vector3(0, 1, 0));
+       Matrix4x4 smat = lt_projmat * lt_viewmat * viewmat.inverse();
+       smat.transpose();
+
+       memcpy(shadow_mat[0], smat[0], 16 * sizeof(float));
+
+       glMatrixMode(GL_PROJECTION);
+       glPushMatrix();
+       glLoadTransposeMatrixf(lt_projmat[0]);
+
+       glMatrixMode(GL_MODELVIEW);
+       glPushMatrix();
+       glLoadTransposeMatrixf(lt_viewmat[0]);
+
+
+       /*gph::Mat4 viewmat;
+       glGetFloatv(GL_MODELVIEW_MATRIX, viewmat[0]);
+
+       gph::Mat4 lt_viewmat, lt_projmat;
+       lt_projmat.perspective(deg_to_rad(lfov) * 2.0, 1.0, znear, zfar);
+       lt_viewmat.inv_lookat(lpos, ltarg, gph::Vec3(0, 1, 0));
+       shadow_mat = lt_projmat * lt_viewmat * viewmat.inverse();
+       //shadow_mat = viewmat.inverse() * lt_viewmat * lt_projmat;
+       shadow_mat.print();
+
+       glMatrixMode(GL_PROJECTION);
+       glPushMatrix();
+       glLoadMatrixf(lt_projmat[0]);
+
+       glMatrixMode(GL_MODELVIEW);
+       glPushMatrix();
+       glLoadMatrixf(lt_viewmat[0]);
+       */
+
+       glGetIntegerv(GL_VIEWPORT, prev_vp);
+       glViewport(0, 0, tex_sz, tex_sz);
+
+       glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+       glPolygonOffset(2, 2);
+       glEnable(GL_POLYGON_OFFSET_FILL);
+
+       glClear(GL_DEPTH_BUFFER_BIT);
+       glUseProgram(0);
+}
+
+
+void end_shadow_pass()
+{
+       shadow_pass = false;
+
+       glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+       glViewport(prev_vp[0], prev_vp[1], prev_vp[2], prev_vp[3]);
+
+       glMatrixMode(GL_PROJECTION);
+       glPopMatrix();
+       glMatrixMode(GL_MODELVIEW);
+       glPopMatrix();
+
+       glPopAttrib();
+}
+
+gph::Mat4 get_shadow_matrix()
+{
+       return shadow_mat;
+}
+
+unsigned int get_shadow_tex()
+{
+       return depth_tex;
+}
diff --git a/src/shadow.h b/src/shadow.h
new file mode 100644 (file)
index 0000000..b40b6f2
--- /dev/null
@@ -0,0 +1,17 @@
+#ifndef SHADOW_H_
+#define SHADOW_H_
+
+#include <gmath/gmath.h>
+
+extern bool shadow_pass;
+
+bool init_shadow(int sz);
+void destroy_shadow();
+
+void begin_shadow_pass(const gph::Vec3 &lpos, const gph::Vec3 &ltarg, float lfov, float znear, float zfar);
+void end_shadow_pass();
+
+gph::Mat4 get_shadow_matrix();
+unsigned int get_shadow_tex();
+
+#endif // SHADOW_H_
diff --git a/src/snode.cc b/src/snode.cc
new file mode 100644 (file)
index 0000000..ffb1d08
--- /dev/null
@@ -0,0 +1,263 @@
+#include <float.h>
+#include <assert.h>
+#include <algorithm>
+#include "snode.h"
+#include "objmesh.h"
+
+SceneNode::SceneNode()
+       : scale(1, 1, 1)
+{
+       scene = 0;
+       parent = 0;
+       name = 0;
+}
+
+SceneNode::SceneNode(Object *obj)
+       : scale(1, 1, 1)
+{
+       scene = 0;
+       parent = 0;
+       name = 0;
+       add_object(obj);
+}
+
+SceneNode::~SceneNode()
+{
+       delete [] name;
+}
+
+void SceneNode::set_name(const char *s)
+{
+       delete [] name;
+       name = new char[strlen(s) + 1];
+       strcpy(name, s);
+}
+
+const char *SceneNode::get_name() const
+{
+       return name;
+}
+
+void SceneNode::add_child(SceneNode *node)
+{
+       if(!node) return;
+
+       if(node->parent) {
+               if(node->parent == this) {
+                       return;
+               }
+               node->parent->remove_child(node);
+       }
+
+       children.push_back(node);
+       node->parent = this;
+       node->scene = scene;
+}
+
+bool SceneNode::remove_child(SceneNode *node)
+{
+       if(!node) return false;
+
+       auto it = std::find(children.begin(), children.end(), node);
+       if(it != children.end()) {
+               assert(node->parent == this);
+               node->parent = 0;
+               node->scene = 0;
+               children.erase(it);
+               return true;
+       }
+       return false;
+}
+
+int SceneNode::get_num_children() const
+{
+       return (int)children.size();
+}
+
+SceneNode *SceneNode::get_child(int idx) const
+{
+       return children[idx];
+}
+
+SceneNode *SceneNode::get_parent() const
+{
+       return parent;
+}
+
+void SceneNode::add_object(Object *obj)
+{
+       if(obj->node == this) return;
+
+       if(obj->node) {
+               obj->node->remove_object(obj);
+       }
+
+       this->obj.push_back(obj);
+       obj->node = this;
+}
+
+bool SceneNode::remove_object(Object *o)
+{
+       if(o->node != this) {
+               return false;
+       }
+       o->node = 0;
+
+       auto it = std::find(obj.begin(), obj.end(), o);
+       if(it == obj.end()) {
+               return false;
+       }
+       obj.erase(it);
+       return true;
+}
+
+int SceneNode::get_num_objects() const
+{
+       return (int)obj.size();
+}
+
+Object *SceneNode::get_object(int idx) const
+{
+       return obj[idx];
+}
+
+void SceneNode::set_position(const Vec3 &pos)
+{
+       this->pos = pos;
+}
+
+void SceneNode::set_rotation(const Quat &rot)
+{
+       this->rot = rot;
+}
+
+void SceneNode::set_scaling(const Vec3 &scale)
+{
+       this->scale = scale;
+}
+
+
+const Vec3 &SceneNode::get_node_position() const
+{
+       return pos;
+}
+
+const Quat &SceneNode::get_node_rotation() const
+{
+       return rot;
+}
+
+const Vec3 &SceneNode::get_node_scaling() const
+{
+       return scale;
+}
+
+
+Vec3 SceneNode::get_position() const
+{
+       return xform * Vec3(0, 0, 0);
+}
+
+Quat SceneNode::get_rotation() const
+{
+       return rot;     // TODO
+}
+
+Vec3 SceneNode::get_scaling() const
+{
+       return scale;   // TODO
+}
+
+const Mat4 &SceneNode::get_matrix() const
+{
+       return xform;
+}
+
+const Mat4 &SceneNode::get_inv_matrix() const
+{
+       return inv_xform;
+}
+
+
+void SceneNode::update_node(float dt)
+{
+       xform = Mat4::identity;
+       xform.pre_translate(pos);
+       xform.pre_rotate(rot);
+       xform.pre_scale(scale);
+
+       if(parent) {
+               xform = xform * parent->xform;
+       }
+       inv_xform = inverse(xform);
+}
+
+void SceneNode::update(float dt)
+{
+       update_node(dt);
+
+       int num = children.size();
+       for(int i=0; i<num; i++) {
+               children[i]->update(dt);
+       }
+}
+
+void SceneNode::apply_xform()
+{
+       update_node();
+
+       // apply post-order to make sure we don't affect the children xform by our reset
+
+       int nchild = children.size();
+       for(int i=0; i<nchild; i++) {
+               children[i]->apply_xform();
+       }
+
+       int nobj = obj.size();
+       for(int i=0; i<nobj; i++) {
+               if(obj[i]->get_type() == OBJ_MESH) {
+                       ObjMesh *om = (ObjMesh*)obj[i];
+                       if(om->mesh) {
+                               om->mesh->apply_xform(xform);
+                       }
+               }
+       }
+
+       pos = Vec3(0, 0, 0);
+       rot = Quat::identity;
+       scale = Vec3(1, 1, 1);
+}
+
+bool SceneNode::intersect(const Ray &ray, HitPoint *hit) const
+{
+       Ray local_ray = inv_xform * ray;
+
+       HitPoint nearest;
+       nearest.dist = FLT_MAX;
+       for(size_t i=0; i<obj.size(); i++) {
+               if(obj[i]->intersect(local_ray, hit)) {
+                       if(!hit) return true;
+                       if(hit->dist < nearest.dist) {
+                               nearest = *hit;
+                               nearest.data = (void*)this;
+                               nearest.local_ray = local_ray;
+                       }
+               }
+       }
+
+       for(size_t i=0; i<children.size(); i++) {
+               if(children[i]->intersect(ray, hit)) {
+                       if(!hit) return true;
+                       if(hit->dist < nearest.dist) {
+                               nearest = *hit;
+                       }
+               }
+       }
+
+       if(nearest.dist < FLT_MAX) {
+               *hit = nearest;
+               hit->ray = ray;
+               return true;
+       }
+       return false;
+}
diff --git a/src/snode.h b/src/snode.h
new file mode 100644 (file)
index 0000000..5af02f4
--- /dev/null
@@ -0,0 +1,74 @@
+#ifndef SNODE_H_
+#define SNODE_H_
+
+#include <vector>
+#include "object.h"
+#include "gmath/gmath.h"
+
+class Scene;
+
+class SceneNode {
+private:
+       char *name;
+
+       Vec3 pos;
+       Quat rot;
+       Vec3 scale;
+
+       std::vector<Object*> obj;
+
+       SceneNode *parent;
+       std::vector<SceneNode*> children;
+
+       Mat4 xform;
+       Mat4 inv_xform;
+
+public:
+       Scene *scene;   // scene to which this node belongs
+       Mat4 dbg_xform;
+
+       SceneNode();
+       explicit SceneNode(Object *obj);
+       ~SceneNode();
+
+       void set_name(const char *s);
+       const char *get_name() const;
+
+       void add_child(SceneNode *node);
+       bool remove_child(SceneNode *node);
+
+       int get_num_children() const;
+       SceneNode *get_child(int idx) const;
+
+       SceneNode *get_parent() const;
+
+       void add_object(Object *obj);
+       bool remove_object(Object *obj);
+
+       int get_num_objects() const;
+       Object *get_object(int idx) const;
+
+       void set_position(const Vec3 &pos);
+       void set_rotation(const Quat &rot);
+       void set_scaling(const Vec3 &scale);
+
+       const Vec3 &get_node_position() const;
+       const Quat &get_node_rotation() const;
+       const Vec3 &get_node_scaling() const;
+
+       Vec3 get_position() const;
+       Quat get_rotation() const;
+       Vec3 get_scaling() const;
+
+       const Mat4 &get_matrix() const;
+       const Mat4 &get_inv_matrix() const;
+
+       void update_node(float dt = 0.0f);
+       void update(float dt = 0.0f);
+
+       void apply_xform();
+
+       bool intersect(const Ray &ray, HitPoint *hit) const;
+};
+
+#endif // SNODE_H_
diff --git a/src/texture.cc b/src/texture.cc
new file mode 100644 (file)
index 0000000..ff4d1b5
--- /dev/null
@@ -0,0 +1,538 @@
+#include <math.h>
+#include <assert.h>
+#include "texture.h"
+#include "datamap.h"
+#include "image.h"
+#include "opengl.h"
+#include "imago2.h"
+#include "logger.h"
+
+static int glifmt_from_ifmt(unsigned int ifmt);
+static int glfmt_from_ifmt(unsigned int ifmt);
+static int gltype_from_ifmt(unsigned int ifmt);
+
+static int glifmt_from_imgfmt(Image::Format fmt);
+
+static unsigned int type_to_target(TextureType type);
+static TextureType target_to_type(unsigned int targ);
+
+static unsigned int cur_target[8] = {
+       GL_TEXTURE_2D, GL_TEXTURE_2D, GL_TEXTURE_2D, GL_TEXTURE_2D,
+       GL_TEXTURE_2D, GL_TEXTURE_2D, GL_TEXTURE_2D, GL_TEXTURE_2D
+};
+
+static unsigned int cube_faces[] = {
+       GL_TEXTURE_CUBE_MAP_POSITIVE_X,
+       GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
+       GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
+       GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
+       GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
+       GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
+};
+
+
+void bind_texture(Texture *tex, int tunit)
+{
+       if(tex) {
+               tex->bind(tunit);
+       } else {
+               glActiveTexture(GL_TEXTURE0 + tunit);
+               glBindTexture(cur_target[tunit], 0);
+               assert(glGetError() == GL_NO_ERROR);
+               glActiveTexture(GL_TEXTURE0);
+       }
+}
+
+
+Image *Texture::default_img;
+
+Texture::Texture()
+{
+       target = 0;
+       sz[0] = sz[1] = sz[2] = 0;
+       texfmt = 0;
+
+       img = 0;
+       glGenTextures(1, &id);
+}
+
+Texture::~Texture()
+{
+       if(id) {
+               glDeleteTextures(1, &id);
+       }
+       if(img) {
+               delete img;
+       }
+}
+
+void Texture::set_wrapping(unsigned int wrap)
+{
+       if(!target) {
+               return;
+       }
+
+       glBindTexture(target, id);
+       assert(glGetError() == GL_NO_ERROR);
+       glTexParameteri(target, GL_TEXTURE_WRAP_S, wrap);
+       glTexParameteri(target, GL_TEXTURE_WRAP_T, wrap);
+       glTexParameteri(target, GL_TEXTURE_WRAP_R, wrap);
+}
+
+void Texture::set_filtering(unsigned int filt)
+{
+       unsigned int mag_filter;
+
+       if(!target) {
+               return;
+       }
+
+       switch(filt) {
+       case GL_LINEAR_MIPMAP_NEAREST:
+       case GL_LINEAR_MIPMAP_LINEAR:
+               mag_filter = GL_LINEAR;
+               break;
+
+       case GL_NEAREST_MIPMAP_NEAREST:
+       case GL_NEAREST_MIPMAP_LINEAR:
+               mag_filter = GL_NEAREST;
+               break;
+
+       default:
+               mag_filter = filt;
+       }
+
+       set_filtering(filt, mag_filter);
+}
+
+void Texture::set_filtering(unsigned int min_filt, unsigned int mag_filt)
+{
+       glBindTexture(target, id);
+       assert(glGetError() == GL_NO_ERROR);
+       glTexParameteri(target, GL_TEXTURE_MIN_FILTER, min_filt);
+       glTexParameteri(target, GL_TEXTURE_MAG_FILTER, mag_filt);
+}
+
+unsigned int Texture::get_format() const
+{
+       return texfmt;
+}
+
+int Texture::get_size(int dim) const
+{
+       if(dim < 0 || dim >= 3) {
+               return 0;
+       }
+       return sz[dim];
+}
+
+unsigned int Texture::get_id() const
+{
+       return id;
+}
+
+TextureType Texture::get_type() const
+{
+       return target_to_type(target);
+}
+
+void Texture::bind(int tex_unit) const
+{
+       glActiveTexture(GL_TEXTURE0 + tex_unit);
+       glBindTexture(target, id);
+       assert(glGetError() == GL_NO_ERROR);
+       glActiveTexture(GL_TEXTURE0);
+
+       cur_target[tex_unit] = target;
+}
+
+
+void Texture::create(int xsz, int ysz, TextureType textype, unsigned int ifmt)
+{
+       if(textype == TEX_CUBE && xsz != ysz) {
+               error_log("trying to create cubemap with different width and height (%dx%d)\n", xsz, ysz);
+               return;
+       }
+
+       int fmt = glfmt_from_ifmt(ifmt);
+       int type = gltype_from_ifmt(ifmt);
+
+       target = type_to_target(textype);
+
+       glBindTexture(target, id);
+       assert(glGetError() == GL_NO_ERROR);
+       glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+       glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+       switch(type) {
+       case TEX_2D:
+               glTexImage2D(GL_TEXTURE_2D, 0, glifmt_from_ifmt(ifmt), xsz, ysz, 0, fmt, type, 0);
+               break;
+
+       case TEX_CUBE:
+               glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+               glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+               glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+               for(int i=0; i<6; i++) {
+                       glTexImage2D(cube_faces[i], 0, ifmt, xsz, ysz, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+               }
+               break;
+       }
+
+       sz[0] = xsz;
+       sz[1] = ysz;
+       texfmt = ifmt;
+}
+
+#define DEF_IMAGE_SIZE 64
+void Texture::create_default(TextureType type)
+{
+       if(!default_img) {
+               default_img = new Image;
+               default_img->create(DEF_IMAGE_SIZE, DEF_IMAGE_SIZE, Image::FMT_RGBA);
+
+               unsigned char *pixels = (unsigned char*)default_img->get_pixels();
+               for(int i=0; i<DEF_IMAGE_SIZE; i++) {
+                       for(int j=0; j<DEF_IMAGE_SIZE; j++) {
+                               bool chess = ((i >> 3) & 1) == ((j >> 3) & 1);
+                               pixels[0] = chess ? 255 : 32;
+                               pixels[1] = 64;
+                               pixels[2] = chess ? 32 : 255;
+                               pixels[3] = 255;
+                               pixels += 4;
+                       }
+               }
+       }
+
+       switch(type) {
+       case TEX_2D:
+               set_image(*default_img);
+               break;
+
+       case TEX_CUBE:
+               for(int i=0; i<6; i++) {
+                       set_image(*default_img, i);
+               }
+               break;
+       }
+}
+
+void Texture::set_image(const Image &img, int idx)
+{
+       if(idx >= 0 && idx < 6) {
+               set_image_cube(img, idx);
+       } else {
+               if(!set_image_cube(img)) {
+                       set_image_2d(img);
+               }
+       }
+}
+
+void Texture::set_image_2d(const Image &img)
+{
+       texfmt = glifmt_from_imgfmt(img.get_format());
+       unsigned int fmt = glfmt_from_ifmt(texfmt);
+       unsigned int type = gltype_from_ifmt(texfmt);
+
+       sz[0] = img.get_width();
+       sz[1] = img.get_height();
+
+       target = GL_TEXTURE_2D;
+       glBindTexture(target, id);
+       assert(glGetError() == GL_NO_ERROR);
+       glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+       glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+       glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_REPEAT);
+       glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_REPEAT);
+
+       glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+#ifdef __GLEW_H__
+       if(GLEW_SGIS_generate_mipmap) {
+               glTexParameteri(target, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);
+#endif
+               glTexImage2D(target, 0, texfmt, sz[0], sz[1], 0, fmt, type, img.get_pixels());
+#ifdef __GLEW_H__
+       } else {
+               gluBuild2DMipmaps(target, texfmt, sz[0], sz[1], fmt, type, img.get_pixels());
+       }
+#endif
+
+#ifdef GL_ES_VERSION_2_0
+       glGenerateMipmap(target);
+#endif
+}
+
+bool Texture::set_image_cube(const Image &img, int idx)
+{
+       unsigned int err;
+       if(idx < 0 || idx >= 6) {
+               return false;
+       }
+
+       texfmt = glifmt_from_imgfmt(img.get_format());
+       unsigned int fmt = glfmt_from_ifmt(texfmt);
+       unsigned int type = gltype_from_ifmt(texfmt);
+
+       sz[0] = img.get_width();
+       sz[1] = img.get_height();
+
+       target = GL_TEXTURE_CUBE_MAP;
+       glBindTexture(target, id);
+       if((err = glGetError()) == GL_INVALID_OPERATION) {
+               return false;   // probably not a cubemap
+       }
+       assert(err == GL_NO_ERROR);
+       glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+       glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+       glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+       glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+       glTexParameteri(target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+
+       glTexImage2D(cube_faces[idx], 0, texfmt, sz[0], sz[1], 0, fmt, type, img.get_pixels());
+       return true;
+}
+
+bool Texture::set_image_cube(const Image &img)
+{
+       static const float one_third = 1.0 / 3.0;
+       static const float two_thirds = 2.0 / 3.0;
+       static const float hcross[2][6] = {
+               {0.5, 0.0, 0.25, 0.25, 0.25, 0.75}, {one_third, one_third, 0.0, two_thirds, one_third, one_third} };
+       static const float vcross[2][6] = {
+               {two_thirds, 0.0, one_third, one_third, one_third, one_third}, {0.25, 0.25, 0.0, 0.5, 0.25, 0.75} };
+       static const float hsix[2][6] = {
+               {0.0, 0.0, one_third, one_third, two_thirds, two_thirds}, {0.0, 0.5, 0.0, 0.5, 0.0, 0.5} };
+
+       int xsz = img.get_width();
+       int ysz = img.get_height();
+
+       if((xsz << 8) / 4 == (ysz << 8) / 3) {
+               // horizontal cross, assume the vertical bit is center-left
+               return set_cube_multi(img, hcross[0], hcross[1], xsz / 4);
+       }
+       if((xsz << 8) / 3 == (ysz << 8) / 4) {
+               // vertical cross, assume the horizontal bit is center-top (180-rotated image 5)
+               return set_cube_multi(img, vcross[0], vcross[1], ysz / 4, (1 << 5));
+       }
+       if((xsz << 8) / 3 == (ysz << 8) / 2) {
+               // horizontal sixpack
+               return set_cube_multi(img, hsix[0], hsix[1], ysz / 2);
+       }
+
+       return false;
+}
+
+
+bool Texture::load(const char *fname)
+{
+       Image img;
+       if(!img.load(fname)) {
+               error_log("failed to load 2D texture: %s\n", fname);
+               return false;
+       }
+       set_image(img);
+
+       info_log("loaded 2D texture: %s\n", fname);
+       return true;
+}
+
+bool Texture::load_cube(const char *fname)
+{
+       Image img;
+       if(!img.load(fname)) {
+               return false;
+       }
+       return set_image_cube(img);
+}
+
+bool Texture::set_cube_multi(const Image &img, const float *xoffsets, const float *yoffsets, float sz,
+               unsigned int rotmask)
+{
+       for(int i=0; i<6; i++) {
+               Image face;
+
+               int xoffs = xoffsets[i] * img.get_width();
+               int yoffs = yoffsets[i] * img.get_height();
+
+               if(!face.set_pixels(sz, sz, img.get_pixels(), xoffs, yoffs, img.get_width(), img.get_format())) {
+                       return false;
+               }
+
+               if(rotmask & (1 << i)) {
+                       face.rotate_180();
+               }
+               set_image_cube(face, i);
+       }
+       return true;
+}
+
+static int glifmt_from_ifmt(unsigned int ifmt)
+{
+#ifdef GL_ES_VERSION_2_0
+       switch(ifmt) {
+       case GL_LUMINANCE16F_ARB:
+       case GL_LUMINANCE32F_ARB:
+               ifmt = GL_LUMINANCE;
+               break;
+
+       case GL_RGB16F:
+       case GL_RGB32F:
+               ifmt = GL_RGB;
+               break;
+
+       case GL_RGBA16F:
+       case GL_RGBA32F:
+               ifmt = GL_RGBA;
+               break;
+
+       default:
+               break;
+       }
+#endif
+       return ifmt;    // by default just pass it through...
+}
+
+static int glfmt_from_ifmt(unsigned int ifmt)
+{
+       switch(ifmt) {
+       case GL_LUMINANCE16F_ARB:
+       case GL_LUMINANCE32F_ARB:
+       case GL_SLUMINANCE:
+               return GL_LUMINANCE;
+
+       case GL_RGB16F:
+       case GL_RGB32F:
+       case GL_SRGB:
+               return GL_RGB;
+
+       case GL_RGBA16F:
+       case GL_RGBA32F:
+       case GL_SRGB_ALPHA:
+               return GL_RGBA;
+
+       default:
+               break;
+       }
+       return ifmt;
+}
+
+static int gltype_from_ifmt(unsigned int ifmt)
+{
+       switch(ifmt) {
+       case GL_RGB16F:
+       case GL_RGBA16F:
+       case GL_LUMINANCE16F_ARB:
+#ifdef GL_ES_VERSION_2_0
+               return GL_HALF_FLOAT_OES;
+#endif
+       case GL_RGB32F:
+       case GL_RGBA32F:
+       case GL_LUMINANCE32F_ARB:
+               return GL_FLOAT;
+
+       default:
+               break;
+       }
+       return GL_UNSIGNED_BYTE;
+}
+
+static int glifmt_from_imgfmt(Image::Format fmt)
+{
+       switch(fmt) {
+       case Image::FMT_GREY:
+               return GL_SLUMINANCE;
+       case Image::FMT_GREY_FLOAT:
+               return GL_LUMINANCE16F_ARB;
+       case Image::FMT_RGB:
+               return GL_SRGB;
+       case Image::FMT_RGB_FLOAT:
+               return GL_RGB16F;
+       case Image::FMT_RGBA:
+               return GL_SRGB_ALPHA;
+       case Image::FMT_RGBA_FLOAT:
+               return GL_RGBA16F;
+       default:
+               break;
+       }
+       return 0;
+}
+
+static unsigned int type_to_target(TextureType type)
+{
+       return type == TEX_CUBE ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
+}
+
+static TextureType target_to_type(unsigned int targ)
+{
+       return targ == GL_TEXTURE_CUBE_MAP ? TEX_CUBE : TEX_2D;
+}
+
+// ---- TextureSet ----
+TextureSet::TextureSet()
+       : DataSet<Texture*>(create_tex, load_tex, done_tex, free_tex)
+{
+}
+
+Texture *TextureSet::get_texture(const char *name, TextureType type, const DataMap *dmap) const
+{
+       char *fname;
+       int nsize = dmap ? dmap->path_size(name) : 0;
+       if(nsize) {
+               fname = (char*)alloca(nsize);
+               dmap->lookup(name, fname, nsize);
+               //debug_log("texture lookup: %s -> %s\n", name, fname);
+       } else {
+               fname = (char*)name;
+               //debug_log("texture lookup failed, using: %s\n", fname);
+       }
+
+       std::map<std::string, Texture*>::const_iterator iter = data.find(fname);
+       if(iter != data.end()) {
+               return iter->second;
+       }
+
+       Texture *res = create();
+       data[fname] = res;
+       res->create_default(type);
+       resman_lookup(rman, fname, res);
+       return res;
+}
+
+// static callbacks
+
+Texture *TextureSet::create_tex()
+{
+       return new Texture;
+}
+
+bool TextureSet::load_tex(Texture *tex, const char *fname)
+{
+       Image *img = new Image;
+       img->name = fname;
+       if(!img->load(fname)) {
+               delete img;
+               return false;
+       }
+
+       delete tex->img;
+       tex->img = img;
+
+       return true;
+}
+
+bool TextureSet::done_tex(Texture *tex)
+{
+       //debug_log("TextureSet::done_tex [%s]\n", tex->img->name.c_str());
+       if(!tex->img) {
+               return false;
+       }
+
+       tex->set_image(*tex->img);
+       return true;
+}
+
+void TextureSet::free_tex(Texture *tex)
+{
+       delete tex;
+}
diff --git a/src/texture.h b/src/texture.h
new file mode 100644 (file)
index 0000000..5e18e7a
--- /dev/null
@@ -0,0 +1,75 @@
+#ifndef TEXTURE_H_
+#define TEXTURE_H_
+
+#include "dataset.h"
+#include "datamap.h"
+#include "opengl.h"
+
+class Image;
+
+enum TextureType { TEX_2D, TEX_CUBE };
+
+class Texture {
+private:
+       unsigned int id;
+       unsigned int target;
+       unsigned int texfmt;
+       int sz[3];
+       Image *img;
+       static Image *default_img;
+
+       Texture(const Texture &tex) {}
+       Texture &operator =(const Texture &tex) { return *this; }
+
+       void set_image_2d(const Image &img);
+       bool set_image_cube(const Image &img, int idx);
+       bool set_image_cube(const Image &img);
+
+       bool load_cube(const char *fname);
+
+       /* for loading multiple cubemap faces from a single image */
+       bool set_cube_multi(const Image &img, const float *xoffsets, const float *yoffsets, float sz,
+               unsigned int rotmask = 0);
+
+public:
+       Texture();
+       ~Texture();
+
+       void set_wrapping(unsigned int wrap);
+       void set_filtering(unsigned int filt);
+       void set_filtering(unsigned int min_filt, unsigned int mag_filt);
+
+       unsigned int get_format() const;
+
+       int get_size(int dim) const;
+
+       void create(int xsz, int ysz, TextureType type = TEX_2D, unsigned int ifmt = GL_RGBA);
+       void create_default(TextureType type = TEX_2D);
+       void set_image(const Image &img, int idx = -1);
+
+       bool load(const char *fname);
+
+       unsigned int get_id() const;
+       TextureType get_type() const;
+
+       void bind(int tex_unit = 0) const;
+
+       friend class TextureSet;
+};
+
+void bind_texture(Texture *tex, int tunit = 0);
+
+class TextureSet : public DataSet<Texture*> {
+private:
+       static Texture *create_tex();
+       static bool load_tex(Texture *tex, const char *fname);
+       static bool done_tex(Texture *tex);
+       static void free_tex(Texture *tex);
+
+public:
+       TextureSet();
+
+       Texture *get_texture(const char *name, TextureType type = TEX_2D, const DataMap *dmap = 0) const;
+};
+
+#endif // TEXTURE_H_
diff --git a/src/ui.cc b/src/ui.cc
new file mode 100644 (file)
index 0000000..a0babe4
--- /dev/null
+++ b/src/ui.cc
@@ -0,0 +1,185 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <gmath/gmath.h>
+#include <drawtext.h>
+#include "opengl.h"
+#include "ui.h"
+#include "app.h"
+
+#define FONTSZ 16
+
+static bool init();
+
+struct Message {
+       long start_time, show_until;
+       char *str;
+       Vec3 color;
+       Message *next;
+};
+static Message *msglist;
+
+struct Text {
+       char *str;
+       Vec2 pos;
+       Vec3 color;
+       Text *next;
+};
+static Text *txlist;
+
+static long timeout = 2000;
+static long trans_time = 250;
+static dtx_font *font;
+
+void set_message_timeout(long tm)
+{
+       timeout = tm;
+}
+
+void show_message(const char *fmt, ...)
+{
+       va_list ap;
+       va_start(ap, fmt);
+       show_messagev(timeout, Vec3(1, 1, 1), fmt, ap);
+       va_end(ap);
+}
+
+void show_message(long timeout, const Vec3 &color, const char *fmt, ...)
+{
+       va_list ap;
+       va_start(ap, fmt);
+       show_messagev(timeout, color, fmt, ap);
+       va_end(ap);
+}
+
+void show_messagev(long timeout, const Vec3 &color, const char *fmt, va_list ap)
+{
+       char buf[512];
+
+       init();
+
+       vsnprintf(buf, sizeof buf, fmt, ap);
+
+       Message *msg = new Message;
+       int len = strlen(buf);
+       msg->str = new char[len + 1];
+       memcpy(msg->str, buf, len + 1);
+       msg->start_time = time_msec;
+       msg->show_until = time_msec + timeout;
+       msg->color = color;
+
+       Message dummy;
+       dummy.next = msglist;
+       Message *prev = &dummy;
+       while(prev->next && prev->next->show_until < msg->show_until) {
+               prev = prev->next;
+       }
+       msg->next = prev->next;
+       prev->next = msg;
+       msglist = dummy.next;
+}
+
+void print_text(const Vec2 &pos, const Vec3 &color, const char *fmt, ...)
+{
+       va_list ap;
+       va_start(ap, fmt);
+       print_textv(pos, color, fmt, ap);
+       va_end(ap);
+}
+
+void print_textv(const Vec2 &pos, const Vec3 &color, const char *fmt, va_list ap)
+{
+       char buf[512];
+
+       init();
+
+       vsnprintf(buf, sizeof buf, fmt, ap);
+
+       Text *tx = new Text;
+       int len = strlen(buf);
+       tx->str = new char[len + 1];
+       memcpy(tx->str, buf, len + 1);
+       tx->color = color;
+       tx->pos = Vec2(pos.x, -pos.y);
+
+       tx->next = txlist;
+       txlist = tx;
+}
+
+void draw_ui()
+{
+       if(!font) return;
+
+       while(msglist && msglist->show_until <= time_msec) {
+               Message *msg = msglist;
+               msglist = msg->next;
+               delete [] msg->str;
+               delete msg;
+       }
+
+       dtx_use_font(font, FONTSZ);
+
+       glMatrixMode(GL_PROJECTION);
+       glPushMatrix();
+       glLoadIdentity();
+       glOrtho(0, win_width, -win_height, 0, -1, 1);
+       glMatrixMode(GL_MODELVIEW);
+       glPushMatrix();
+       glLoadIdentity();
+
+       glPushAttrib(GL_ENABLE_BIT);
+       glDisable(GL_LIGHTING);
+       glDisable(GL_DEPTH_TEST);
+       glEnable(GL_BLEND);
+       glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+       glUseProgram(0);
+
+       Message *msg = msglist;
+       while(msg) {
+               long t = time_msec - msg->start_time;
+               long dur = msg->show_until - msg->start_time;
+               float alpha = smoothstep(0, trans_time, t) *
+                       (1.0 - smoothstep(dur - trans_time, dur, t));
+               glColor4f(msg->color.x, msg->color.y, msg->color.z, alpha);
+               glTranslatef(0, -dtx_line_height(), 0);
+               dtx_string(msg->str);
+               msg = msg->next;
+       }
+
+       while(txlist) {
+               Text *tx = txlist;
+               txlist = txlist->next;
+
+               glMatrixMode(GL_MODELVIEW);
+               glLoadIdentity();
+               glTranslatef(tx->pos.x, tx->pos.y, 0);
+
+               glColor3f(tx->color.x, tx->color.y, tx->color.z);
+               dtx_string(tx->str);
+
+               delete [] tx->str;
+               delete tx;
+       }
+
+       glPopAttrib();
+
+       glMatrixMode(GL_PROJECTION);
+       glPopMatrix();
+       glMatrixMode(GL_MODELVIEW);
+       glPopMatrix();
+}
+
+static bool init()
+{
+       static bool done_init;
+       if(done_init) return true;
+
+       done_init = true;
+
+       if(!(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);
+       return true;
+}
diff --git a/src/ui.h b/src/ui.h
new file mode 100644 (file)
index 0000000..088fb82
--- /dev/null
+++ b/src/ui.h
@@ -0,0 +1,17 @@
+#ifndef UI_H_
+#define UI_H_
+
+#include <stdarg.h>
+#include <gmath/gmath.h>
+
+void set_message_timeout(long timeout);
+void show_message(const char *fmt, ...);
+void show_message(long timeout, const Vec3 &color, const char *fmt, ...);
+void show_messagev(long timeout, const Vec3 &color, const char *fmt, va_list ap);
+
+void print_text(const Vec2 &pos, const Vec3 &color, const char *fmt, ...);
+void print_textv(const Vec2 &pos, const Vec3 &color, const char *fmt, va_list ap);
+
+void draw_ui();
+
+#endif // UI_H_