--- /dev/null
+*.o
+*.swp
+*.d
+oneroom
+data/
+.clang_complete
+*.suo
+*sdf
+Debug/
+Release/
--- /dev/null
+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)
--- /dev/null
+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;
+}
--- /dev/null
+void main()
+{
+ gl_Position = gl_Vertex;
+ gl_TexCoord[0] = gl_MultiTexCoord0;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+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);
+}
--- /dev/null
+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;
+}
--- /dev/null
+varying vec3 normal;
+
+void main()
+{
+ gl_Position = ftransform();
+ normal = gl_Normal;
+}
--- /dev/null
+#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;
+ }
+}
--- /dev/null
+#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_
--- /dev/null
+#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;
+}
--- /dev/null
+#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_
--- /dev/null
+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}
+};
--- /dev/null
+/*
+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 */
--- /dev/null
+/*
+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_ */
--- /dev/null
+#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;
+}
--- /dev/null
+#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_
--- /dev/null
+/** 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_
--- /dev/null
+#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);
+ }
+}
--- /dev/null
+#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();
+ }
+}
--- /dev/null
+#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_
--- /dev/null
+#include <algorithm>
+#include <float.h>
+#include "geom.h"
+
+GeomObject::~GeomObject()
+{
+}
+
+
+Sphere::Sphere()
+{
+ radius = 1.0;
+}
+
+Sphere::Sphere(const Vec3 ¢, 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);
+}
--- /dev/null
+#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 ¢er, float radius);
+
+ GeomObjectType get_type() const;
+
+ void set_union(const GeomObject *obj1, const GeomObject *obj2);
+ void set_intersection(const GeomObject *obj1, const GeomObject *obj2);
+
+ 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_
--- /dev/null
+#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);
+}
--- /dev/null
+#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_
--- /dev/null
+#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;
+}
--- /dev/null
+#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_
--- /dev/null
+#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;
+ }
+}
--- /dev/null
+#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";
+}
--- /dev/null
+#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_
--- /dev/null
+#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_
--- /dev/null
+#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;
+}
--- /dev/null
+#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_
--- /dev/null
+#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;
+ }
+}
--- /dev/null
+#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_
--- /dev/null
+#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);
+ }
+}
--- /dev/null
+#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_
--- /dev/null
+#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
+{
+}
--- /dev/null
+#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_
--- /dev/null
+#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();
+ }
+}
--- /dev/null
+#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_
--- /dev/null
+#ifndef OPENGL_H_
+#define OPENGL_H_
+
+#include <GL/glew.h>
+
+#endif // OPENGL_H_
--- /dev/null
+#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;
+}
--- /dev/null
+#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_
--- /dev/null
+#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);
+}
--- /dev/null
+#ifndef POST_H_
+#define POST_H_
+
+void slow_post(unsigned int sdr);
+
+#endif // POST_H_
--- /dev/null
+#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();
+ }
+ }
+}
--- /dev/null
+#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_
--- /dev/null
+#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;
+}
--- /dev/null
+#ifndef SCENELOAD_H_
+#define SCENELOAD_H_
+
+#include "scene.h"
+
+bool load_scene(
+
+#endif // SCENELOAD_H_
--- /dev/null
+#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;
+}
--- /dev/null
+#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_ */
--- /dev/null
+#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 <arg, 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;
+}
--- /dev/null
+#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 <arg, float lfov, float znear, float zfar);
+void end_shadow_pass();
+
+gph::Mat4 get_shadow_matrix();
+unsigned int get_shadow_tex();
+
+#endif // SHADOW_H_
--- /dev/null
+#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;
+}
--- /dev/null
+#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_
--- /dev/null
+#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;
+}
--- /dev/null
+#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_
--- /dev/null
+#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;
+}
--- /dev/null
+#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_