From 82ebd0ea8b766c75fb7226bf72892ffae01f4c11 Mon Sep 17 00:00:00 2001 From: John Tsiombikas Date: Sun, 11 Dec 2016 01:43:53 +0200 Subject: [PATCH] initial commit --- .gitignore | 10 + Makefile | 45 ++ sdr/post_gamma.p.glsl | 10 + sdr/post_gamma.v.glsl | 5 + sdr/shadow-notex.p.glsl | 60 ++ sdr/shadow.v.glsl | 27 + sdr/skydome.p.glsl | 12 + sdr/skydome.v.glsl | 7 + src/app.cc | 589 +++++++++++++++++++ src/app.h | 70 +++ src/blob_exhibit.cc | 144 +++++ src/blob_exhibit.h | 23 + src/blobs/mcubes.h | 294 ++++++++++ src/blobs/metasurf.c | 452 +++++++++++++++ src/blobs/metasurf.h | 79 +++ src/datamap.cc | 153 +++++ src/datamap.h | 25 + src/dataset.h | 53 ++ src/dataset.inl | 99 ++++ src/exhibit.cc | 41 ++ src/exhibit.h | 32 ++ src/geom.cc | 294 ++++++++++ src/geom.h | 95 ++++ src/image.cc | 299 ++++++++++ src/image.h | 49 ++ src/logger.cc | 129 +++++ src/logger.h | 18 + src/main.cc | 226 ++++++++ src/material.cc | 95 ++++ src/material.h | 43 ++ src/mech_exhibit.h | 18 + src/mesh.cc | 1443 +++++++++++++++++++++++++++++++++++++++++++++++ src/mesh.h | 248 ++++++++ src/meshgen.cc | 883 +++++++++++++++++++++++++++++ src/meshgen.h | 23 + src/metascene.cc | 353 ++++++++++++ src/metascene.h | 32 ++ src/object.cc | 37 ++ src/object.h | 37 ++ src/objmesh.cc | 33 ++ src/objmesh.h | 18 + src/opengl.h | 6 + src/opt.cc | 114 ++++ src/opt.h | 15 + src/post.cc | 67 +++ src/post.h | 6 + src/scene.cc | 254 +++++++++ src/scene.h | 90 +++ src/sceneload.cc | 395 +++++++++++++ src/sceneload.h | 8 + src/sdr.c | 563 ++++++++++++++++++ src/sdr.h | 68 +++ src/shadow.cc | 147 +++++ src/shadow.h | 17 + src/snode.cc | 263 +++++++++ src/snode.h | 74 +++ src/texture.cc | 538 ++++++++++++++++++ src/texture.h | 75 +++ src/ui.cc | 185 ++++++ src/ui.h | 17 + 60 files changed, 9505 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 sdr/post_gamma.p.glsl create mode 100644 sdr/post_gamma.v.glsl create mode 100644 sdr/shadow-notex.p.glsl create mode 100644 sdr/shadow.v.glsl create mode 100644 sdr/skydome.p.glsl create mode 100644 sdr/skydome.v.glsl create mode 100644 src/app.cc create mode 100644 src/app.h create mode 100644 src/blob_exhibit.cc create mode 100644 src/blob_exhibit.h create mode 100644 src/blobs/mcubes.h create mode 100644 src/blobs/metasurf.c create mode 100644 src/blobs/metasurf.h create mode 100644 src/datamap.cc create mode 100644 src/datamap.h create mode 100644 src/dataset.h create mode 100644 src/dataset.inl create mode 100644 src/exhibit.cc create mode 100644 src/exhibit.h create mode 100644 src/geom.cc create mode 100644 src/geom.h create mode 100644 src/image.cc create mode 100644 src/image.h create mode 100644 src/logger.cc create mode 100644 src/logger.h create mode 100644 src/main.cc create mode 100644 src/material.cc create mode 100644 src/material.h create mode 100644 src/mech_exhibit.h create mode 100644 src/mesh.cc create mode 100644 src/mesh.h create mode 100644 src/meshgen.cc create mode 100644 src/meshgen.h create mode 100644 src/metascene.cc create mode 100644 src/metascene.h create mode 100644 src/object.cc create mode 100644 src/object.h create mode 100644 src/objmesh.cc create mode 100644 src/objmesh.h create mode 100644 src/opengl.h create mode 100644 src/opt.cc create mode 100644 src/opt.h create mode 100644 src/post.cc create mode 100644 src/post.h create mode 100644 src/scene.cc create mode 100644 src/scene.h create mode 100644 src/sceneload.cc create mode 100644 src/sceneload.h create mode 100644 src/sdr.c create mode 100644 src/sdr.h create mode 100644 src/shadow.cc create mode 100644 src/shadow.h create mode 100644 src/snode.cc create mode 100644 src/snode.h create mode 100644 src/texture.cc create mode 100644 src/texture.h create mode 100644 src/ui.cc create mode 100644 src/ui.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9d47d3e --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*.o +*.swp +*.d +oneroom +data/ +.clang_complete +*.suo +*sdf +Debug/ +Release/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ec3a5c3 --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +src = $(wildcard src/*.cc) $(wildcard src/blobs/*.cc) +csrc = $(wildcard src/*.c) $(wildcard src/blobs/*.c) +obj = $(src:.cc=.o) $(csrc:.c=.o) +dep = $(obj:.o=.d) +bin = oneroom + +#opt = -O3 -ffast-math +dbg = -g + +incpath = -Isrc -I/usr/local/include `pkg-config --cflags sdl2` +libpath = -L/usr/local/lib + +warn = -pedantic -Wall + +CFLAGS = $(warn) $(opt) $(dbg) $(incpath) +CXXFLAGS = -std=c++11 $(warn) $(opt) $(dbg) $(incpath) +LDFLAGS = $(libpath) $(libgl_$(sys)) -lm -lgmath -lvmath -limago -lresman \ + -lpthread -lassimp -ltreestore -ldrawtext -loptcfg -lgoatvr `pkg-config --libs sdl2` + +sys = $(shell uname -s) +libgl_Linux = -lGL -lGLU -lGLEW +libgl_Darwin = -framework OpenGL -lGLEW + +$(bin): .clang_complete $(obj) + $(CXX) -o $@ $(obj) $(LDFLAGS) + +.clang_complete: Makefile + rm -f $@ + for i in $(CXXFLAGS); do echo $$i >>$@; done + +-include $(dep) + +%.d: %.c + @$(CPP) $(CFLAGS) $< -MM -MT $(@:.d=.o) >$@ + +%.d: %.cc + @$(CPP) $(CXXFLAGS) $< -MM -MT $(@:.d=.o) >$@ + +.PHONY: clean +clean: + rm -f $(obj) $(bin) + +.PHONY: cleandep +cleandep: + rm -f $(dep) diff --git a/sdr/post_gamma.p.glsl b/sdr/post_gamma.p.glsl new file mode 100644 index 0000000..6b323f8 --- /dev/null +++ b/sdr/post_gamma.p.glsl @@ -0,0 +1,10 @@ +uniform sampler2D tex; + +void main() +{ + const vec3 invgamma3 = vec3(1.0 / 2.2); + vec3 texel = texture2D(tex, gl_TexCoord[0].st).xyz; + + gl_FragColor.rgb = pow(texel, invgamma3); + gl_FragColor.a = 1.0; +} diff --git a/sdr/post_gamma.v.glsl b/sdr/post_gamma.v.glsl new file mode 100644 index 0000000..a8fb0cf --- /dev/null +++ b/sdr/post_gamma.v.glsl @@ -0,0 +1,5 @@ +void main() +{ + gl_Position = gl_Vertex; + gl_TexCoord[0] = gl_MultiTexCoord0; +} diff --git a/sdr/shadow-notex.p.glsl b/sdr/shadow-notex.p.glsl new file mode 100644 index 0000000..4946ca0 --- /dev/null +++ b/sdr/shadow-notex.p.glsl @@ -0,0 +1,60 @@ +/* vi: set ft=glsl */ +#ifdef USE_CUBEMAP +uniform samplerCube envmap; +#endif +uniform sampler2DShadow shadowmap; + +varying vec3 vdir, ldir[3], normal; +varying vec4 shadow_tc; +varying vec3 wdir; + +#define KD gl_FrontMaterial.diffuse.rgb +#define KS gl_FrontMaterial.specular.rgb +#define SPOW gl_FrontMaterial.shininess + +#define LD(i) gl_LightSource[i].diffuse.rgb +#define LS(i) gl_LightSource[i].specular.rgb + +vec3 calc_diffuse(in vec3 n, in vec3 l, in vec3 lcol) +{ + float ndotl = max(dot(n, l), 0.0); + return KD * lcol * ndotl; +} + +vec3 calc_specular(in vec3 n, in vec3 l, in vec3 v, in vec3 lcol) +{ + vec3 h = normalize(l + v); + float ndoth = max(dot(n, h), 0.0); + return KS * lcol * pow(ndoth, SPOW); +} + +void main() +{ + float shadow = shadow2DProj(shadowmap, shadow_tc).x; + + vec3 n = normalize(normal); + vec3 v = normalize(vdir); + + vec3 l = normalize(ldir[0]); + vec3 diffuse = calc_diffuse(n, l, LD(0)) * shadow; + vec3 specular = calc_specular(n, l, v, LS(0)) * shadow; + + l = normalize(ldir[1]); + diffuse += calc_diffuse(n, l, LD(1)); + specular += calc_specular(n, l, v, LS(1)); + + l = normalize(ldir[2]); + diffuse += calc_diffuse(n, l, LD(2)); + specular += calc_specular(n, l, v, LS(2)); + +#ifdef USE_CUBEMAP + // envmap + vec3 rdir = -reflect(wdir, n); + vec3 env_texel = textureCube(envmap, rdir).xyz; + specular += KS * env_texel; +#endif // USE_CUBEMAP + + vec3 ambient = gl_LightModel.ambient.rgb * KD; + gl_FragColor.rgb = ambient + diffuse + specular; + gl_FragColor.a = gl_FrontMaterial.diffuse.a; +} diff --git a/sdr/shadow.v.glsl b/sdr/shadow.v.glsl new file mode 100644 index 0000000..8f9f045 --- /dev/null +++ b/sdr/shadow.v.glsl @@ -0,0 +1,27 @@ +uniform mat4 envmap_matrix; + +varying vec3 vdir, ldir[3], normal; +varying vec4 shadow_tc; +varying vec3 wdir; + +void main() +{ + gl_Position = ftransform(); + + vec3 vpos = (gl_ModelViewMatrix * gl_Vertex).xyz; + normal = gl_NormalMatrix * gl_Normal; + vdir = -vpos; + wdir = (envmap_matrix * vec4(vdir, 1.0)).xyz; // bring back to world space + ldir[0] = gl_LightSource[0].position.xyz - vpos; + ldir[1] = gl_LightSource[1].position.xyz - vpos; + ldir[2] = gl_LightSource[2].position.xyz - vpos; + gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0; + + mat4 offmat = mat4(0.5, 0.0, 0.0, 0.0, + 0.0, 0.5, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 0.5, 0.5, 0.5, 1.0); + mat4 tex_matrix = offmat * gl_TextureMatrix[1]; + + shadow_tc = tex_matrix * vec4(vpos, 1.0); +} diff --git a/sdr/skydome.p.glsl b/sdr/skydome.p.glsl new file mode 100644 index 0000000..7682150 --- /dev/null +++ b/sdr/skydome.p.glsl @@ -0,0 +1,12 @@ +uniform samplerCube envmap; + +varying vec3 normal; + +void main() +{ + vec3 n = normalize(normal); + vec3 texel = textureCube(envmap, n).xyz; + + gl_FragColor.rgb = texel; + gl_FragColor.a = 1.0; +} diff --git a/sdr/skydome.v.glsl b/sdr/skydome.v.glsl new file mode 100644 index 0000000..c5d3124 --- /dev/null +++ b/sdr/skydome.v.glsl @@ -0,0 +1,7 @@ +varying vec3 normal; + +void main() +{ + gl_Position = ftransform(); + normal = gl_Normal; +} diff --git a/src/app.cc b/src/app.cc new file mode 100644 index 0000000..4b2ff2b --- /dev/null +++ b/src/app.cc @@ -0,0 +1,589 @@ +#include +#include +#include +#include "app.h" +#include "opengl.h" +#include "sdr.h" +#include "texture.h" +#include "mesh.h" +#include "meshgen.h" +#include "scene.h" +#include "metascene.h" +#include "datamap.h" +#include "ui.h" +#include "opt.h" +#include "post.h" +#include "blob_exhibit.h" + +#define NEAR_CLIP 0.5 +#define FAR_CLIP 1000.0 + +static void draw_scene(); +static void toggle_flight(); +static void calc_framerate(); + +long time_msec; +int win_width, win_height; +float win_aspect; +bool fb_srgb; +bool opt_gear_wireframe; + +TextureSet texman; +SceneSet sceneman; + +unsigned int sdr_shadow, sdr_shadow_notex; + +static float cam_dist = 0.0; +static float cam_theta, cam_phi; +static Vec3 cam_pos; +static float floor_y; // last floor height +static float user_eye_height = 1.65; + +static float walk_speed = 3.0f; +static float mouse_speed = 0.5f; +static bool show_walk_mesh, noclip = false; + +static bool have_headtracking, should_swap; + +static int prev_mx, prev_my; +static bool bnstate[8]; +static bool keystate[256]; +static bool gpad_bnstate[64]; +static Vec2 joy_move, joy_look; +static float joy_deadzone = 0.01; + +static float framerate; + +static Mat4 view_matrix, mouse_view_matrix, proj_matrix; +static MetaScene *mscn; +static unsigned int sdr_post_gamma; + +static long prev_msec; + +static BlobExhibit *blobs; +static bool show_blobs = 1; + + +bool app_init(int argc, char **argv) +{ + if(!init_options(argc, argv, "demo.conf")) { + return false; + } + app_resize(opt.width, opt.height); + app_fullscreen(opt.fullscreen); + + if(opt.vr) { + if(goatvr_init() == -1) { + return false; + } + goatvr_set_origin_mode(GOATVR_HEAD); + + goatvr_startvr(); + should_swap = goatvr_should_swap() != 0; + user_eye_height = goatvr_get_eye_height(); + have_headtracking = goatvr_have_headtracking(); + + goatvr_recenter(); + } + + int srgb_capable; + glGetIntegerv(GL_FRAMEBUFFER_SRGB_CAPABLE_EXT, &srgb_capable); + printf("Framebuffer %s sRGB-capable\n", srgb_capable ? "is" : "is not"); + fb_srgb = srgb_capable != 0; + glEnable(GL_FRAMEBUFFER_SRGB); + + glEnable(GL_MULTISAMPLE); + glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + glEnable(GL_LIGHTING); + glEnable(GL_NORMALIZE); + + Mesh::use_custom_sdr_attr = false; + + float ambient[] = {0.0, 0.0, 0.0, 0.0}; + glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient); + + glClearColor(0.1, 0.1, 0.1, 1.0); + + mscn = new MetaScene; + if(!mscn->load(opt.scenefile ? opt.scenefile : "data/room.scene")) { + return false; + } + + cam_pos = mscn->start_pos; + debug_log("start_pos: [%g %g %g]\n", cam_pos.x, cam_pos.y, cam_pos.z); + Vec3 dir = rotate(Vec3(0, 0, 1), mscn->start_rot); + dir.y = 0; + cam_theta = rad_to_deg(acos(dot(dir, Vec3(0, 0, 1)))); + + blobs = new BlobExhibit; + blobs->node = new SceneNode; + blobs->init(); + blobs->node->set_position(Vec3(0, 1, 0)); + blobs->node->update(0); + + if(!(sdr_shadow_notex = create_program_load("sdr/shadow.v.glsl", "sdr/shadow-notex.p.glsl"))) { + return false; + } + set_uniform_int(sdr_shadow_notex, "shadowmap", 1); + set_uniform_int(sdr_shadow_notex, "envmap", 2); + + if(!fb_srgb) { + sdr_post_gamma = create_program_load("sdr/post_gamma.v.glsl", "sdr/post_gamma.p.glsl"); + } + + glUseProgram(0); + + if(opt.vr || opt.fullscreen) { + app_grab_mouse(true); + } + return true; +} + +void app_cleanup() +{ + app_grab_mouse(false); + if(opt.vr) { + goatvr_shutdown(); + } + + blobs->destroy(); + delete blobs->node; + delete blobs; + + texman.clear(); + sceneman.clear(); +} + +static bool constrain_walk_mesh(const Vec3 &v, Vec3 *newv) +{ + Mesh *wm = mscn->walk_mesh; + if(!wm) { + *newv = v; + return true; + } + + Ray downray = Ray(v, Vec3(0, -1, 0)); + HitPoint hit; + if(mscn->walk_mesh->intersect(downray, &hit)) { + *newv = hit.pos; + newv->y += user_eye_height; + return true; + } + return false; +} + +static void update(float dt) +{ + texman.update(); + sceneman.update(); + + mscn->update(dt); + if(show_blobs) { + blobs->update(dt); + } + + float speed = walk_speed * dt; + Vec3 dir; + + // joystick + float jdeadsq = joy_deadzone * joy_deadzone; + float jmove_lensq = length_sq(joy_move); + float jlook_lensq = length_sq(joy_look); + + if(jmove_lensq > jdeadsq) { + float len = sqrt(jmove_lensq); + jmove_lensq -= jdeadsq; + + float mag = len * len; + dir.x += mag * joy_move.x / len * 2.0 * speed; + dir.z += mag * joy_move.y / len * 2.0 * speed; + } + if(jlook_lensq > jdeadsq) { + float len = sqrt(jlook_lensq); + jlook_lensq -= jdeadsq; + + float mag = len * len; + cam_theta += mag * joy_look.x / len * 200.0 * dt; + cam_phi += mag * joy_look.y / len * 100.0 * dt; + if(cam_phi < -90.0f) cam_phi = -90.0f; + if(cam_phi > 90.0f) cam_phi = 90.0f; + } + + // keyboard move + if(keystate[(int)'w']) { + dir.z -= speed; + } + if(keystate[(int)'s']) { + dir.z += speed; + } + if(keystate[(int)'d']) { + dir.x += speed; + } + if(keystate[(int)'a']) { + dir.x -= speed; + } + if(keystate[(int)'q'] || gpad_bnstate[GPAD_UP]) { + cam_pos.y += speed; + } + if(keystate[(int)'z'] || gpad_bnstate[GPAD_DOWN]) { + cam_pos.y -= speed; + } + + float theta = M_PI * cam_theta / 180.0f; + Vec3 newpos; + newpos.x = cam_pos.x + cos(theta) * dir.x - sin(theta) * dir.z; + newpos.y = cam_pos.y; + newpos.z = cam_pos.z + sin(theta) * dir.x + cos(theta) * dir.z; + + if(noclip) { + cam_pos = newpos; + } else { + if(!constrain_walk_mesh(newpos, &cam_pos)) { + float dtheta = M_PI / 32.0; + float theta = dtheta; + Vec2 dir2d = newpos.xz() - cam_pos.xz(); + + for(int i=0; i<16; i++) { + Vec2 dvec = rotate(dir2d, theta); + Vec3 pos = cam_pos + Vec3(dvec.x, 0, dvec.y); + if(constrain_walk_mesh(pos, &cam_pos)) { + break; + } + dvec = rotate(dir2d, -theta); + pos = cam_pos + Vec3(dvec.x, 0, dvec.y); + if(constrain_walk_mesh(pos, &cam_pos)) { + break; + } + theta += dtheta; + } + } + floor_y = cam_pos.y - user_eye_height; + } + + // calculate mouselook view matrix + mouse_view_matrix = Mat4::identity; + mouse_view_matrix.pre_translate(0, 0, -cam_dist); + if(!have_headtracking) { + mouse_view_matrix.pre_rotate_x(deg_to_rad(cam_phi)); + } + mouse_view_matrix.pre_rotate_y(deg_to_rad(cam_theta)); + mouse_view_matrix.pre_translate(-cam_pos.x, -cam_pos.y, -cam_pos.z); +} + +static void set_light(int idx, const Vec3 &pos, const Vec3 &color) +{ + unsigned int lt = GL_LIGHT0 + idx; + float posv[] = { pos.x, pos.y, pos.z, 1 }; + float colv[] = { color.x, color.y, color.z, 1 }; + + glEnable(lt); + glLightfv(lt, GL_POSITION, posv); + glLightfv(lt, GL_DIFFUSE, colv); + glLightfv(lt, GL_SPECULAR, colv); +} + +void app_display() +{ + float dt = (float)(time_msec - prev_msec) / 1000.0f; + prev_msec = time_msec; + + update(dt); + + if(opt.vr) { + // VR mode + goatvr_draw_start(); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + for(int i=0; i<2; i++) { + // for each eye + goatvr_draw_eye(i); + + proj_matrix = goatvr_projection_matrix(i, NEAR_CLIP, FAR_CLIP); + glMatrixMode(GL_PROJECTION); + glLoadMatrixf(proj_matrix[0]); + + view_matrix = mouse_view_matrix * Mat4(goatvr_view_matrix(i)); + glMatrixMode(GL_MODELVIEW); + glLoadMatrixf(view_matrix[0]); + + draw_scene(); + } + goatvr_draw_done(); + + if(should_swap) { + app_swap_buffers(); + } + + } else { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + proj_matrix.perspective(deg_to_rad(50.0), win_aspect, NEAR_CLIP, FAR_CLIP); + glMatrixMode(GL_PROJECTION); + glLoadMatrixf(proj_matrix[0]); + + view_matrix = mouse_view_matrix; + glMatrixMode(GL_MODELVIEW); + glLoadMatrixf(view_matrix[0]); + + draw_scene(); + + if(!fb_srgb && sdr_post_gamma) { + slow_post(sdr_post_gamma); + glUseProgram(0); + } + app_swap_buffers(); + } + assert(glGetError() == GL_NO_ERROR); + + calc_framerate(); +} + + +static void draw_scene() +{ + static const Vec3 lpos[] = { Vec3(-50, 75, 100), Vec3(100, 0, 30), Vec3(-10, -10, 60) }; + set_light(0, lpos[0], Vec3(1.0, 0.8, 0.7) * 0.8); + set_light(1, lpos[1], Vec3(0.6, 0.7, 1.0) * 0.6); + set_light(2, lpos[2], Vec3(0.8, 1.0, 0.8) * 0.3); + + mscn->draw(); + if(show_blobs) { + blobs->draw(); + } + + if(show_walk_mesh && mscn->walk_mesh) { + glPushAttrib(GL_ENABLE_BIT); + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE); + glDisable(GL_LIGHTING); + glEnable(GL_POLYGON_OFFSET_FILL); + + glUseProgram(0); + + glPolygonOffset(-1, 1); + glDepthMask(0); + + glColor3f(0.3, 0.08, 0.01); + mscn->walk_mesh->draw(); + + glDepthMask(1); + + glPopAttrib(); + } + + print_text(Vec2(9 * win_width / 10, 20), Vec3(1, 1, 0), "fps: %.1f", framerate); + draw_ui(); +} + + +void app_reshape(int x, int y) +{ + glViewport(0, 0, x, y); + goatvr_set_fb_size(x, y, 1.0f); +} + +void app_keyboard(int key, bool pressed) +{ + unsigned int mod = app_get_modifiers(); + + if(pressed) { + switch(key) { + case 27: + app_quit(); + break; + + case '\n': + case '\r': + if(mod & MOD_ALT) { + app_toggle_fullscreen(); + } + break; + + case '`': + app_toggle_grab_mouse(); + show_message("mouse %s", app_is_mouse_grabbed() ? "grabbed" : "released"); + break; + + case 'w': + if(mod & MOD_CTRL) { + show_walk_mesh = !show_walk_mesh; + show_message("walk mesh: %s", show_walk_mesh ? "on" : "off"); + } + break; + + case 'c': + if(mod & MOD_CTRL) { + noclip = !noclip; + show_message(noclip ? "no clip" : "clip"); + } + break; + + case 'f': + toggle_flight(); + break; + + case 'p': + if(mod & MOD_CTRL) { + fb_srgb = !fb_srgb; + show_message("gamma correction for non-sRGB framebuffers: %s\n", fb_srgb ? "off" : "on"); + } + break; + + case '=': + walk_speed *= 1.25; + show_message("walk speed: %g", walk_speed); + break; + + case '-': + walk_speed *= 0.75; + show_message("walk speed: %g", walk_speed); + break; + + case ']': + mouse_speed *= 1.2; + show_message("mouse speed: %g", mouse_speed); + break; + + case '[': + mouse_speed *= 0.8; + show_message("mouse speed: %g", mouse_speed); + break; + + case 'b': + show_blobs = !show_blobs; + show_message("blobs: %s\n", show_blobs ? "on" : "off"); + break; + } + } + + if(key < 256 && !(mod & (MOD_CTRL | MOD_ALT))) { + keystate[key] = pressed; + } +} + +void app_mouse_button(int bn, bool pressed, int x, int y) +{ + prev_mx = x; + prev_my = y; + bnstate[bn] = pressed; +} + +static inline void mouse_look(float dx, float dy) +{ + float scrsz = (float)win_height; + cam_theta += dx * 512.0 / scrsz; + cam_phi += dy * 512.0 / scrsz; + + if(cam_phi < -90) cam_phi = -90; + if(cam_phi > 90) cam_phi = 90; +} + +static void mouse_zoom(float dx, float dy) +{ + cam_dist += dy * 0.1; + if(cam_dist < 0.0) cam_dist = 0.0; +} + +void app_mouse_motion(int x, int y) +{ + int dx = x - prev_mx; + int dy = y - prev_my; + prev_mx = x; + prev_my = y; + + if(!dx && !dy) return; + + if(bnstate[0]) { + mouse_look(dx, dy); + } + if(bnstate[2]) { + mouse_zoom(dx, dy); + } +} + +void app_mouse_delta(int dx, int dy) +{ + if(bnstate[2]) { + mouse_zoom(dx * mouse_speed, dy * mouse_speed); + } else { + mouse_look(dx * mouse_speed, dy * mouse_speed); + } +} + +void app_gamepad_axis(int axis, float val) +{ + switch(axis) { + case 0: + joy_move.x = val; + break; + case 1: + joy_move.y = val; + break; + + case 2: + joy_look.x = val; + break; + case 3: + joy_look.y = val; + break; + } +} + +void app_gamepad_button(int bn, bool pressed) +{ + gpad_bnstate[bn] = pressed; + + if(pressed) { + switch(bn) { + case GPAD_LSTICK: + toggle_flight(); + break; + + case GPAD_X: + show_blobs = !show_blobs; + show_message("blobs: %s\n", show_blobs ? "on" : "off"); + break; + + default: + break; + } + } +} + +static void toggle_flight() +{ + static float prev_walk_speed = -1.0; + if(prev_walk_speed < 0.0) { + noclip = true; + prev_walk_speed = walk_speed; + walk_speed = 10.0; + show_message("fly mode\n"); + } else { + noclip = false; + walk_speed = prev_walk_speed; + prev_walk_speed = -1.0; + show_message("walk mode\n"); + } +} + +static void calc_framerate() +{ + //static int ncalc; + static int nframes; + static long prev_upd; + + long elapsed = time_msec - prev_upd; + if(elapsed >= 1000) { + framerate = (float)nframes / (float)(elapsed * 0.001); + nframes = 1; + prev_upd = time_msec; + + /*if(++ncalc >= 5) { + printf("fps: %f\n", framerate); + ncalc = 0; + }*/ + } else { + ++nframes; + } +} diff --git a/src/app.h b/src/app.h new file mode 100644 index 0000000..603cc34 --- /dev/null +++ b/src/app.h @@ -0,0 +1,70 @@ +#ifndef APP_H_ +#define APP_H_ + +#include "texture.h" +#include "scene.h" + +extern long time_msec; +extern int win_width, win_height; +extern float win_aspect; +extern bool opt_gear_wireframe; +extern bool fb_srgb; + +extern TextureSet texman; +extern SceneSet sceneman; + +extern unsigned int sdr_shadow, sdr_shadow_notex; + +enum { + MOD_SHIFT = 1, + MOD_ALT = 2, + MOD_CTRL = 4 +}; + +/* XXX make sure these match with SDL_GameControllerButton */ +enum { + GPAD_A, + GPAD_B, + GPAD_X, + GPAD_Y, + GPAD_BACK, + GPAD_GUIDE, + GPAD_START, + GPAD_LSTICK, + GPAD_RSTICK, + GPAD_L, + GPAD_R, + GPAD_UP, + GPAD_DOWN, + GPAD_LEFT, + GPAD_RIGHT, +}; + +bool app_init(int argc, char **argv); +void app_cleanup(); + +void app_display(); +void app_reshape(int x, int y); + +void app_keyboard(int key, bool pressed); +void app_mouse_button(int bn, bool pressed, int x, int y); +void app_mouse_motion(int x, int y); +void app_mouse_delta(int dx, int dy); + +void app_gamepad_axis(int axis, float val); +void app_gamepad_button(int bn, bool pressed); + +// the following functions are implemented by the backend (main.cc) +void app_quit(); +void app_swap_buffers(); +unsigned int app_get_modifiers(); + +void app_resize(int x, int y); +void app_fullscreen(bool fs); +void app_toggle_fullscreen(); +bool app_is_fullscreen(); +void app_grab_mouse(bool grab); +void app_toggle_grab_mouse(); +bool app_is_mouse_grabbed(); + +#endif // APP_H_ diff --git a/src/blob_exhibit.cc b/src/blob_exhibit.cc new file mode 100644 index 0000000..0378359 --- /dev/null +++ b/src/blob_exhibit.cc @@ -0,0 +1,144 @@ +#include "blob_exhibit.h" +#include "blobs/metasurf.h" +#include "app.h" + +struct Metaball { + Vec3 pos; + float energy; + Vec3 path_scale, path_offset; + float phase_offset, speed; +}; +#define NUM_MBALLS 8 + +static Metaball def_mball_data[] = { + {Vec3(0, 0, 0), 2.18038, Vec3(1.09157, 1.69766, 1), Vec3(0.622818, 0.905624, 0), 1.24125, 0.835223}, + {Vec3(0, 0, 0), 2.03646, Vec3(0.916662, 1.2161, 1), Vec3(0.118734, 0.283516, 0), 2.29201, 1.0134}, + {Vec3(0, 0, 0), 2.40446, Vec3(1.87429, 1.57595, 1), Vec3(0.298566, -0.788474, 0), 3.8137, 0.516301}, + {Vec3(0, 0, 0), 0.985774, Vec3(0.705847, 0.735019, 1), Vec3(0.669189, -0.217922, 0), 0.815497, 0.608809}, + {Vec3(0, 0, 0), 2.49785, Vec3(0.827385, 1.75867, 1), Vec3(0.0284513, 0.247808, 0), 1.86002, 1.13755}, + {Vec3(0, 0, 0), 1.54857, Vec3(1.24037, 0.938775, 1), Vec3(1.04011, 0.596987, 0), 3.30964, 1.26991}, + {Vec3(0, 0, 0), 1.30046, Vec3(1.83729, 1.02869, 1), Vec3(-0.476708, 0.676994, 0), 5.77441, 0.569755}, + {Vec3(0, 0, 0), 2.39865, Vec3(1.28899, 0.788321, 1), Vec3(-0.910677, 0.359099, 0), 5.5935, 0.848893} +}; + +struct BlobPriv { + metasurface *msurf; + Metaball mballs[NUM_MBALLS]; + Texture *tex; +}; + +static void vertex(struct metasurface *ms, float x, float y, float z); +static float eval(struct metasurface *ms, float x, float y, float z); + + +BlobExhibit::BlobExhibit() +{ + priv = new BlobPriv; + for(int i=0; imballs[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; imballs[i].speed + priv->mballs[i].phase_offset, M_PI * 2.0); + priv->mballs[i].pos.x = cos(t) * priv->mballs[i].path_scale.x + priv->mballs[i].path_offset.x; + priv->mballs[i].pos.y = sin(t) * priv->mballs[i].path_scale.y + priv->mballs[i].path_offset.y; + priv->mballs[i].pos.z = -cos(t) * priv->mballs[i].path_scale.z + priv->mballs[i].path_offset.z; + } +} + +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; imballs[i].pos.x; + float dy = y - priv->mballs[i].pos.y; + float dz = z - priv->mballs[i].pos.z; + float dsq = dx * dx + dy * dy + dz * dz; + + sum += priv->mballs[i].energy / dsq; + } + return sum; +} diff --git a/src/blob_exhibit.h b/src/blob_exhibit.h new file mode 100644 index 0000000..001047a --- /dev/null +++ b/src/blob_exhibit.h @@ -0,0 +1,23 @@ +#ifndef BLOB_EXHIBIT_H_ +#define BLOB_EXHIBIT_H_ + +#include "exhibit.h" + +struct BlobPriv; + +class BlobExhibit : public Exhibit { +private: + BlobPriv *priv; + +public: + BlobExhibit(); + ~BlobExhibit(); + + bool init(); + void destroy(); + + void update(float dt); + void draw() const; +}; + +#endif // BLOB_EXHIBIT_H_ diff --git a/src/blobs/mcubes.h b/src/blobs/mcubes.h new file mode 100644 index 0000000..2a9034d --- /dev/null +++ b/src/blobs/mcubes.h @@ -0,0 +1,294 @@ +static int mc_edge_table[256] = { + 0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, + 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, + 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, + 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, + 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c, + 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, + 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac, + 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, + 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c, + 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, + 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc, + 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, + 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c, + 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, + 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc , + 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, + 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, + 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, + 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, + 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, + 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, + 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, + 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, + 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460, + 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, + 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0, + 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, + 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230, + 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, + 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190, + 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, + 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 +}; + + +static int mc_tri_table[256][16] = { + {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1}, + {3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1}, + {3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1}, + {3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1}, + {9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1}, + {1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1}, + {9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1}, + {2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1}, + {8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1}, + {9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1}, + {4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1}, + {3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1}, + {1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1}, + {4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1}, + {4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1}, + {9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1}, + {1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1}, + {5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1}, + {2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1}, + {9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1}, + {0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1}, + {2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1}, + {10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1}, + {4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1}, + {5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1}, + {5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1}, + {9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1}, + {0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1}, + {10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1}, + {8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1}, + {2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1}, + {7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1}, + {9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1}, + {2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1}, + {11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1}, + {9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1}, + {5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1}, + {11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1}, + {11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1}, + {1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1}, + {9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1}, + {5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1}, + {2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1}, + {0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1}, + {5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1}, + {6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1}, + {0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1}, + {3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1}, + {6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1}, + {5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1}, + {1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1}, + {10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1}, + {6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1}, + {1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1}, + {8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1}, + {7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1}, + {3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1}, + {5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1}, + {0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1}, + {9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1}, + {8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1}, + {5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1}, + {0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1}, + {6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1}, + {10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1}, + {10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1}, + {8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1}, + {1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1}, + {3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1}, + {0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1}, + {10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1}, + {0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1}, + {3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1}, + {6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1}, + {9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1}, + {8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1}, + {3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1}, + {6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1}, + {0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1}, + {10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1}, + {10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1}, + {1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1}, + {2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1}, + {7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1}, + {7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1}, + {2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1}, + {1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1}, + {11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1}, + {8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1}, + {0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1}, + {7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1}, + {10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1}, + {2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1}, + {6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1}, + {7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1}, + {2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1}, + {1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1}, + {10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1}, + {10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1}, + {0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1}, + {7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1}, + {6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1}, + {8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1}, + {9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1}, + {6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1}, + {1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1}, + {4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1}, + {10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1}, + {8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1}, + {0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1}, + {1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1}, + {8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1}, + {10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1}, + {4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1}, + {10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1}, + {5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1}, + {11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1}, + {9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1}, + {6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1}, + {7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1}, + {3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1}, + {7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1}, + {9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1}, + {3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1}, + {6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1}, + {9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1}, + {1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1}, + {4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1}, + {7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1}, + {6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1}, + {3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1}, + {0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1}, + {6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1}, + {1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1}, + {0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1}, + {11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1}, + {6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1}, + {5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1}, + {9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1}, + {1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1}, + {10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1}, + {0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1}, + {5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1}, + {10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1}, + {11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1}, + {0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1}, + {9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1}, + {7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1}, + {2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1}, + {8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1}, + {9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1}, + {9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1}, + {1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1}, + {9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1}, + {9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1}, + {5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1}, + {0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1}, + {10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1}, + {2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1}, + {0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1}, + {0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1}, + {9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1}, + {5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1}, + {3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1}, + {5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1}, + {8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1}, + {0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1}, + {9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1}, + {0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1}, + {1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1}, + {3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1}, + {4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1}, + {9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1}, + {11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1}, + {11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1}, + {2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1}, + {9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1}, + {3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1}, + {1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1}, + {4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1}, + {4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1}, + {0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1}, + {3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1}, + {3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1}, + {0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1}, + {9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1}, + {1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1} +}; diff --git a/src/blobs/metasurf.c b/src/blobs/metasurf.c new file mode 100644 index 0000000..2207b3d --- /dev/null +++ b/src/blobs/metasurf.c @@ -0,0 +1,452 @@ +/* +metasurf - a library for implicit surface polygonization +Copyright (C) 2011-2016 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . +*/ +/* this is pulled from: https://github.com/jtsiomb/metasurf */ +#include +#include +#include "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; ires[0] - 1; i++) { + pos[1] = ms->min[1]; + for(j=0; jres[1] - 1; j++) { + + pos[2] = ms->min[2]; + for(k=0; kres[2] - 1; k++) { + + process_cell(ms, pos, delta); + + pos[2] += delta[2]; + } + pos[1] += delta[1]; + } + pos[0] += delta[0]; + } + return 0; +} + + +static void process_cell(struct metasurface *ms, vec3 pos, vec3 sz) +{ + int i; + vec3 p[8]; + float val[8]; + +#ifdef USE_MTETRA + static int tetra[][4] = { + {0, 2, 3, 7}, + {0, 2, 6, 7}, + {0, 4, 6, 7}, + {0, 6, 1, 2}, + {0, 6, 1, 4}, + {5, 6, 1, 4} + }; +#endif + + static const float offs[][3] = { + {0.0f, 0.0f, 0.0f}, + {1.0f, 0.0f, 0.0f}, + {1.0f, 1.0f, 0.0f}, + {0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 1.0f}, + {1.0f, 1.0f, 1.0f}, + {0.0f, 1.0f, 1.0f} + }; + + for(i=0; i<8; i++) { + p[i][0] = pos[0] + sz[0] * offs[i][2]; + p[i][1] = pos[1] + sz[1] * offs[i][1]; + p[i][2] = pos[2] + sz[2] * offs[i][0]; + + val[i] = ms->eval(ms, p[i][0], p[i][1], p[i][2]); + } + +#ifdef USE_MTETRA + for(i=0; i<6; i++) { + process_tetra(ms, tetra[i], p, val); + } +#endif +#ifdef USE_MCUBES + process_cube(ms, p, val); +#endif +} + + +/* ---- marching cubes implementation ---- */ +#ifdef USE_MCUBES + +static unsigned int mc_bitcode(float *val, float thres); + +static void process_cube(struct metasurface *ms, vec3 *pos, float *val) +{ + static const int pidx[12][2] = { + {0, 1}, {1, 2}, {2, 3}, {3, 0}, {4, 5}, {5, 6}, + {6, 7}, {7, 4}, {0, 4}, {1, 5}, {2, 6}, {3, 7} + }; + int i, j; + vec3 vert[12]; + unsigned int code = mc_bitcode(val, ms->thres); + + if(ms->flip) { + code = ~code & 0xff; + } + + if(mc_edge_table[code] == 0) { + return; + } + + for(i=0; i<12; i++) { + if(mc_edge_table[code] & (1 << i)) { + int p0 = pidx[i][0]; + int p1 = pidx[i][1]; + + float t = (ms->thres - val[p0]) / (val[p1] - val[p0]); + vert[i][0] = pos[p0][0] + (pos[p1][0] - pos[p0][0]) * t; + vert[i][1] = pos[p0][1] + (pos[p1][1] - pos[p0][1]) * t; + vert[i][2] = pos[p0][2] + (pos[p1][2] - pos[p0][2]) * t; + } + } + + for(i=0; mc_tri_table[code][i] != -1; i+=3) { + for(j=0; j<3; j++) { + float *v = vert[mc_tri_table[code][i + j]]; + + if(ms->normal) { + float dfdx, dfdy, dfdz; + dfdx = ms->eval(ms, v[0] - ms->dx, v[1], v[2]) - ms->eval(ms, v[0] + ms->dx, v[1], v[2]); + dfdy = ms->eval(ms, v[0], v[1] - ms->dy, v[2]) - ms->eval(ms, v[0], v[1] + ms->dy, v[2]); + dfdz = ms->eval(ms, v[0], v[1], v[2] - ms->dz) - ms->eval(ms, v[0], v[1], v[2] + ms->dz); + + if(ms->flip) { + dfdx = -dfdx; + dfdy = -dfdy; + dfdz = -dfdz; + } + ms->normal(ms, dfdx, dfdy, dfdz); + } + + /* TODO multithreadied polygon emmit */ + ms->vertex(ms, v[0], v[1], v[2]); + } + } +} + +static unsigned int mc_bitcode(float *val, float thres) +{ + unsigned int i, res = 0; + + for(i=0; i<8; i++) { + if(val[i] > thres) { + res |= 1 << i; + } + } + return res; +} +#endif /* USE_MCUBES */ + + +/* ---- marching tetrahedra implementation (incomplete) ---- */ +#ifdef USE_MTETRA + +static unsigned int mt_bitcode(float v0, float v1, float v2, float v3, float thres); +static void emmit(struct metasurface *ms, float v0, float v1, vec3 p0, vec3 p1, int rev) + + +#define REVBIT(x) ((x) & 8) +#define INV(x) (~(x) & 0xf) +#define EDGE(a, b) emmit(ms, val[idx[a]], val[idx[b]], pos[idx[a]], pos[idx[b]], REVBIT(code)) +static void process_tetra(struct metasurface *ms, int *idx, vec3 *pos, float *val) +{ + unsigned int code = mt_bitcode(val[idx[0]], val[idx[1]], val[idx[2]], val[idx[3]], ms->thres); + + switch(code) { + case 1: + case INV(1): + EDGE(0, 1); + EDGE(0, 2); + EDGE(0, 3); + break; + + case 2: + case INV(2): + EDGE(1, 0); + EDGE(1, 3); + EDGE(1, 2); + break; + + case 3: + case INV(3): + EDGE(0, 3); + EDGE(0, 2); + EDGE(1, 3); + + EDGE(1, 3); + EDGE(1, 2); + EDGE(0, 2); + break; + + case 4: + case INV(4): + EDGE(2, 0); + EDGE(2, 1); + EDGE(2, 3); + break; + + case 5: + case INV(5): + EDGE(0, 1); + EDGE(2, 3); + EDGE(0, 3); + + EDGE(0, 1); + EDGE(1, 2); + EDGE(2, 3); + break; + + case 6: + case INV(6): + EDGE(0, 1); + EDGE(1, 3); + EDGE(2, 3); + + EDGE(0, 1); + EDGE(0, 2); + EDGE(2, 3); + break; + + case 7: + case INV(7): + EDGE(3, 0); + EDGE(3, 2); + EDGE(3, 1); + break; + + default: + break; /* cases 0 and 15 */ + } +} + +#define BIT(i) ((v##i > thres) ? (1 << i) : 0) +static unsigned int mt_bitcode(float v0, float v1, float v2, float v3, float thres) +{ + return BIT(0) | BIT(1) | BIT(2) | BIT(3); +} + +static void emmit(struct metasurface *ms, float v0, float v1, vec3 p0, vec3 p1, int rev) +{ + int i; + float t = (ms->thres - v0) / (v1 - v0); + + vec3 p; + for(i=0; i<3; i++) { + p[i] = p0[i] + (p1[i] - p0[i]) * t; + } + ms->vertex(ms, p[0], p[1], p[2]); + + /*for(i=0; i<3; i++) { + ms->vbuf[ms->nverts][i] = p0[i] + (p1[i] - p0[i]) * t; + } + + if(++ms->nverts >= 3) { + ms->nverts = 0; + + for(i=0; i<3; i++) { + int idx = rev ? (2 - i) : i; + ms->vertex(ms, ms->vbuf[idx][0], ms->vbuf[idx][1], ms->vbuf[idx][2]); + } + }*/ +} +#endif /* USE_MTETRA */ diff --git a/src/blobs/metasurf.h b/src/blobs/metasurf.h new file mode 100644 index 0000000..77d2920 --- /dev/null +++ b/src/blobs/metasurf.h @@ -0,0 +1,79 @@ +/* +metasurf - a library for implicit surface polygonization +Copyright (C) 2011-2015 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . +*/ +/* this is pulled from: https://github.com/jtsiomb/metasurf */ +#ifndef METASURF_H_ +#define METASURF_H_ + +#define MSURF_GREATER 1 +#define MSURF_LESS 0 + +struct metasurface; + +typedef float (*msurf_eval_func_t)(struct metasurface *ms, float, float, float); +typedef void (*msurf_vertex_func_t)(struct metasurface *ms, float, float, float); +typedef void (*msurf_normal_func_t)(struct metasurface *ms, float, float, float); + +#ifdef __cplusplus +extern "C" { +#endif + +struct metasurface *msurf_create(void); +void msurf_free(struct metasurface *ms); + +void msurf_set_user_data(struct metasurface *ms, void *udata); +void *msurf_get_user_data(struct metasurface *ms); + +/* which is inside above or below the threshold */ +void msurf_set_inside(struct metasurface *ms, int inside); +int msurf_get_inside(struct metasurface *ms); + +/* set a scalar field evaluator function */ +void msurf_eval_func(struct metasurface *ms, msurf_eval_func_t func); + +/* set a generated vertex callback function */ +void msurf_vertex_func(struct metasurface *ms, msurf_vertex_func_t func); + +/* set a generated surface normal callback function (unused yet) */ +void msurf_normal_func(struct metasurface *ms, msurf_normal_func_t func); + +/* set the bounding box (default: -1, -1, -1, 1, 1, 1) + * keep this as tight as possible to avoid wasting grid resolution + */ +void msurf_set_bounds(struct metasurface *ms, float xmin, float ymin, float zmin, float xmax, float ymax, float zmax); +void msurf_get_bounds(struct metasurface *ms, float *xmin, float *ymin, float *zmin, float *xmax, float *ymax, float *zmax); + +/* resolution of the 3D evaluation grid, the bigger, the better, the slower + * (default: 40, 40, 40) + */ +void msurf_set_resolution(struct metasurface *ms, int xres, int yres, int zres); +void msurf_get_resolution(struct metasurface *ms, int *xres, int *yres, int *zres); + +/* isosurface threshold value (default: 0) */ +void msurf_set_threshold(struct metasurface *ms, float thres); +float msurf_get_threshold(struct metasurface *ms); + + +/* finally call this to perform the polygonization */ +int msurf_polygonize(struct metasurface *ms); + + +#ifdef __cplusplus +} +#endif + +#endif /* METASURF_H_ */ diff --git a/src/datamap.cc b/src/datamap.cc new file mode 100644 index 0000000..e466082 --- /dev/null +++ b/src/datamap.cc @@ -0,0 +1,153 @@ +#include +#include +#include +#include "datamap.h" + +#ifdef WIN32 +#include +#else +#include +#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 pair; + //pair.first = std::regex(line); + std::pair 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 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::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 s && isspace(*end)) --end; + if(s == end) return 0; + end[1] = 0; + + // app-specific: convert backslashes + char *c = s; + while(*c) { + if(*c == '\\') *c = '/'; + ++c; + } + + return s; +} diff --git a/src/datamap.h b/src/datamap.h new file mode 100644 index 0000000..137fdca --- /dev/null +++ b/src/datamap.h @@ -0,0 +1,25 @@ +#ifndef DATAMAP_H_ +#define DATAMAP_H_ + +#include +#include +#include + +class DataMap { + std::vector> dmap; + mutable std::map cache; + std::string root; + +public: + void clear(); + + void set_path(const char *path); + + bool load_map(const char *fname); + void map(const char *match, const char *path); + + int lookup(const char *in, char *buf, int bsz) const; + int path_size(const char *in) const; +}; + +#endif // DATAMAP_H_ diff --git a/src/dataset.h b/src/dataset.h new file mode 100644 index 0000000..a4736c4 --- /dev/null +++ b/src/dataset.h @@ -0,0 +1,53 @@ +/** DataSet is a generic resource database with fast O(logn) lookups by name + * it can be used for texture managers, mesh managers, sound effect managers etc + * + * The constructor takes a load function and a destructor function to be called + * when a nonexistent resource is requested and needs to be loaded, and when + * the DataSet is destroyed. The destructor is optional and can be set to null + * if not needed. + * + * Requesting a resource works by simply calling get, example: + * ---------------------------------------------------------- + * \code + * Texture *load_texture(const char *fname); + * void free_texture(Texture *tex); + * + * DataSet texman(load_texture, free_texture); + * Texture *foo = texman.get("foo.png"); + * \endcode + */ +#ifndef DATASET_H_ +#define DATASET_H_ + +#include +#include +#include + +template +class DataSet { +protected: + mutable std::map data; + mutable struct resman *rman; + + T (*create)(); + bool (*load)(T, const char*); + bool (*done)(T); + void (*destroy)(T); + + static int dataset_load_func(const char *fname, int id, void *cls); + static int dataset_done_func(int id, void *cls); + static void dataset_destroy_func(int id, void *cls); + +public: + DataSet(T (*create_func)(), bool (*load_func)(T, const char*), bool (*done_func)(T) = 0, void (*destr_func)(T) = 0); + ~DataSet(); + + void clear(); + void update(); + + T get(const char *name) const; +}; + +#include "dataset.inl" + +#endif // DATASET_H_ diff --git a/src/dataset.inl b/src/dataset.inl new file mode 100644 index 0000000..ca01ea2 --- /dev/null +++ b/src/dataset.inl @@ -0,0 +1,99 @@ +#include +#include +#include "logger.h" + +template +DataSet::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 +DataSet::~DataSet() +{ + resman_free(rman); +} + +template +void DataSet::clear() +{ + resman_free(rman); + data.clear(); + + rman = resman_create(); +} + +template +void DataSet::update() +{ + resman_poll(rman); +} + +template +T DataSet::get(const char *name) const +{ + typename std::map::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 +int DataSet::dataset_load_func(const char *fname, int id, void *cls) +{ + DataSet *dset = (DataSet*)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 +int DataSet::dataset_done_func(int id, void *cls) +{ + DataSet *dset = (DataSet*)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 +void DataSet::dataset_destroy_func(int id, void *cls) +{ + DataSet *dset = (DataSet*)cls; + T data = (T)resman_get_res_data(dset->rman, id); + + if(dset->destroy) { + dset->destroy(data); + } +} diff --git a/src/exhibit.cc b/src/exhibit.cc new file mode 100644 index 0000000..8653343 --- /dev/null +++ b/src/exhibit.cc @@ -0,0 +1,41 @@ +#include "exhibit.h" +#include "snode.h" + +Exhibit::Exhibit() +{ +} + +void *Exhibit::select(const Ray &ray) const +{ + return 0; // TODO +} + +void *Exhibit::select(const Sphere &sph) const +{ + return 0; // TODO +} + +void Exhibit::update(float dt) +{ +} + +void Exhibit::pre_draw() const +{ + if(node) { + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glMultMatrixf(node->get_matrix()[0]); + } +} + +void Exhibit::draw() const +{ +} + +void Exhibit::post_draw() const +{ + if(node) { + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + } +} diff --git a/src/exhibit.h b/src/exhibit.h new file mode 100644 index 0000000..c4c0ec9 --- /dev/null +++ b/src/exhibit.h @@ -0,0 +1,32 @@ +#ifndef EXHIBIT_H_ +#define EXHIBIT_H_ + +#include +#include "object.h" +#include "geom.h" + +/* +- select me aktina kai select me sfaira, epistrefei Selection +- hover me aktina kai hover me sfaira +- move me selection, origin, direction kai rotation (?) + */ + +class Exhibit : public Object { +public: + Exhibit(); + virtual ~Exhibit() = default; + + Exhibit(const Exhibit&) = delete; + Exhibit &operator =(const Exhibit &) = delete; + + virtual void *select(const Ray &ray) const; + virtual void *select(const Sphere &sph) const; + + virtual void update(float dt = 0.0f); + + virtual void pre_draw() const; + virtual void draw() const; + virtual void post_draw() const; +}; + +#endif // EXHIBIT_H_ diff --git a/src/geom.cc b/src/geom.cc new file mode 100644 index 0000000..55cfed7 --- /dev/null +++ b/src/geom.cc @@ -0,0 +1,294 @@ +#include +#include +#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); +} diff --git a/src/geom.h b/src/geom.h new file mode 100644 index 0000000..e110193 --- /dev/null +++ b/src/geom.h @@ -0,0 +1,95 @@ +#ifndef GEOMOBJ_H_ +#define GEOMOBJ_H_ + +#include + +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_ diff --git a/src/image.cc b/src/image.cc new file mode 100644 index 0000000..e79073d --- /dev/null +++ b/src/image.cc @@ -0,0 +1,299 @@ +#include + +#ifndef _MSC_VER +#include +#else +#include +#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 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 + +class Image { +public: + enum Format { + FMT_GREY, + FMT_RGB, + FMT_RGBA, + FMT_GREY_FLOAT, + FMT_RGB_FLOAT, + FMT_RGBA_FLOAT + }; + +private: + Format fmt; + int width, height; + void *pixels; + +public: + std::string name; + + Image(); + ~Image(); + + int get_width() const; + int get_height() const; + + Format get_format() const; + + bool create(int x, int y, Format fmt = FMT_RGBA); + bool set_pixels(int xsz, int ysz, void *pixels, Format fmt = FMT_RGBA); + bool set_pixels(int xsz, int ysz, void *pixels, int scan_width, Format fmt = FMT_RGBA); + bool set_pixels(int xsz, int ysz, void *pixels, int x, int y, int scan_width = -1, Format fmt = FMT_RGBA); + void *get_pixels() const; + + void flip_horizontal(); + void flip_vertical(); + void rotate_180(); + + void resize_half(); + + bool load(const char *fname); + bool save(const char *fname) const; +}; + +#endif // IMAGE_H_ diff --git a/src/logger.cc b/src/logger.cc new file mode 100644 index 0000000..015c9bd --- /dev/null +++ b/src/logger.cc @@ -0,0 +1,129 @@ +#include +#include +#include "logger.h" +#include "ui.h" + +#if defined(unix) || defined(__unix__) || defined(__APPLE__) +#include +#elif defined(WIN32) +#include +#endif + +#define UI_MSG_TIMEOUT 4000 + +enum { LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_FATAL, LOG_DEBUG }; + +static int typecolor(int type); + +static FILE *fp = stdout; + +static void logmsg(int type, const char *fmt, va_list ap) +{ +#if defined(unix) || defined(__unix__) || (defined(__APPLE__) && !defined(TARGET_IPHONE)) + if(isatty(fileno(fp)) && type != LOG_INFO) { + int c = typecolor(type); + fprintf(fp, "\033[%dm", c); + vfprintf(fp, fmt, ap); + fprintf(fp, "\033[0m"); + } else +#endif + { + vfprintf(fp, fmt, ap); + } + if(type == LOG_ERROR || type == LOG_FATAL || type == LOG_DEBUG) { + fflush(fp); + } + +#ifdef WIN32 + if(type == LOG_FATAL) { + static char msgbuf[1024]; + vsnprintf(msgbuf, sizeof msgbuf - 1, fmt, ap); + msgbuf[sizeof msgbuf - 1] = 0; + MessageBox(0, msgbuf, "Fatal error", MB_OK | MB_ICONSTOP); + } +#endif +} + +extern "C" void info_log(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + logmsg(LOG_INFO, fmt, ap); + va_end(ap); +} + +extern "C" void warning_log(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + logmsg(LOG_WARNING, fmt, ap); + va_end(ap); + + va_start(ap, fmt); + show_messagev(UI_MSG_TIMEOUT, Vec3(1.0, 0.8, 0.1), fmt, ap); + va_end(ap); +} + +extern "C" void error_log(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + logmsg(LOG_ERROR, fmt, ap); + va_end(ap); + + va_start(ap, fmt); + show_messagev(UI_MSG_TIMEOUT, Vec3(1.0, 0.1, 0.1), fmt, ap); + va_end(ap); +} + +extern "C" void fatal_log(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + logmsg(LOG_FATAL, fmt, ap); + va_end(ap); +} + +extern "C" void debug_log(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + logmsg(LOG_DEBUG, fmt, ap); + va_end(ap); +} + +enum { + BLACK = 0, + RED, + GREEN, + YELLOW, + BLUE, + MAGENTA, + CYAN, + WHITE +}; + +#define ANSI_FGCOLOR(x) (30 + (x)) +#define ANSI_BGCOLOR(x) (40 + (x)) + +static int typecolor(int type) +{ + switch(type) { + case LOG_ERROR: + return ANSI_FGCOLOR(RED); + case LOG_FATAL: + return ANSI_FGCOLOR(RED); // TODO differentiate from LOG_ERROR + case LOG_WARNING: + return ANSI_FGCOLOR(YELLOW); + case LOG_DEBUG: + return ANSI_FGCOLOR(MAGENTA); + default: + break; + } + return 37; +} diff --git a/src/logger.h b/src/logger.h new file mode 100644 index 0000000..978d634 --- /dev/null +++ b/src/logger.h @@ -0,0 +1,18 @@ +#ifndef LOGGER_H_ +#define LOGGER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +void info_log(const char *fmt, ...); +void warning_log(const char *fmt, ...); +void error_log(const char *fmt, ...); +void fatal_log(const char *fmt, ...); +void debug_log(const char *fmt, ...); + +#ifdef __cplusplus +} +#endif + +#endif // LOGGER_H_ diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 0000000..eff77a5 --- /dev/null +++ b/src/main.cc @@ -0,0 +1,226 @@ +#include +#include +#include +#include +#include +#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; itype) { + case SDL_QUIT: + quit = true; + break; + + case SDL_KEYDOWN: + case SDL_KEYUP: + proc_modkeys(); + app_keyboard(ev->key.keysym.sym, ev->key.state == SDL_PRESSED); + break; + + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + proc_modkeys(); + app_mouse_button(ev->button.button - SDL_BUTTON_LEFT, ev->button.state == SDL_PRESSED, + ev->button.x * scale_factor, ev->button.y * scale_factor); + break; + + case SDL_MOUSEMOTION: + if(mouse_grabbed) { + app_mouse_delta(ev->motion.xrel, ev->motion.yrel); + } else { + app_mouse_motion(ev->motion.x * scale_factor, ev->motion.y * scale_factor); + } + break; + + case SDL_WINDOWEVENT: + if(ev->window.event == SDL_WINDOWEVENT_RESIZED) { + SDL_GL_GetDrawableSize(win, &win_width, &win_height); + win_aspect = (float)win_width / (float)win_height; + scale_factor = win_width / ev->window.data1; + app_reshape(win_width, win_height); + } + break; + + case SDL_CONTROLLERAXISMOTION: + app_gamepad_axis(ev->caxis.axis, ev->caxis.value / 32768.0f); + break; + + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + app_gamepad_button(ev->cbutton.button, ev->type == SDL_CONTROLLERBUTTONDOWN); + break; + } +} + +static void proc_modkeys() +{ + modkeys = 0; + SDL_Keymod sdlmod = SDL_GetModState(); + if(sdlmod & KMOD_SHIFT) { + modkeys |= MOD_SHIFT; + } + if(sdlmod & KMOD_ALT) { + modkeys |= MOD_ALT; + } + if(sdlmod & KMOD_CTRL) { + modkeys |= MOD_CTRL; + } +} diff --git a/src/material.cc b/src/material.cc new file mode 100644 index 0000000..189b6ad --- /dev/null +++ b/src/material.cc @@ -0,0 +1,95 @@ +#include +#include +#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::iterator it = std::find(textures.begin(), textures.end(), tex); + if(it != textures.end()) { + textures.erase(it); + } + + for(int i=0; i +#include +#include +#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 textures; + + Material(); + void setup() const; + + void add_texture(Texture *tex, int type = MTL_TEX_UNKNOWN); + void remove_texture(Texture *tex); +}; + +// returns MTL_TEX_whatever by name +int mtl_parse_type(const char *str); +// returns the name of a material type +const char *mtl_type_string(int type); + +#endif // MATERIAL_H_ diff --git a/src/mech_exhibit.h b/src/mech_exhibit.h new file mode 100644 index 0000000..74fa613 --- /dev/null +++ b/src/mech_exhibit.h @@ -0,0 +1,18 @@ +#ifndef MECH_EXHIBIT_H_ +#define MECH_EXHIBIT_H_ + +#include "exhibit.h" +#include "machine.h" + +class MechExhibit : public Exhibit { +private: + +public: + MechExhibit(); + ~MechExhibit(); + + void update(float dt); + void draw() const; +}; + +#endif // MECH_EXHIBIT_H_ diff --git a/src/mesh.cc b/src/mesh.cc new file mode 100644 index 0000000..6a1faa7 --- /dev/null +++ b/src/mesh.cc @@ -0,0 +1,1443 @@ +#include +#include +#include +#include +#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; iname = 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) { + 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; iget_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) { + 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= (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= 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= 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; icalc_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 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 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 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; iget_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 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 bsph.radius) { + bsph.radius = dist_sq; + } + } + bsph.radius = sqrt(bsph.radius); + + bsph_valid = true; +} + +void Mesh::update_buffers() +{ + for(int i=0; iget_index_data(); + + for(int i=0; icalc_normal(); + } + return normal; +} + +void Triangle::transform(const Mat4 &xform) +{ + v[0] = xform * v[0]; + v[1] = xform * v[1]; + v[2] = xform * v[2]; + normal_valid = false; +} + +void Triangle::draw() const +{ + Vec3 n[3]; + n[0] = n[1] = n[2] = get_normal(); + + int vloc = Mesh::get_attrib_location(MESH_ATTR_VERTEX); + int nloc = Mesh::get_attrib_location(MESH_ATTR_NORMAL); + + glEnableVertexAttribArray(vloc); + glVertexAttribPointer(vloc, 3, GL_FLOAT, GL_FALSE, 0, &v[0].x); + glVertexAttribPointer(nloc, 3, GL_FLOAT, GL_FALSE, 0, &n[0].x); + + glDrawArrays(GL_TRIANGLES, 0, 3); + + glDisableVertexAttribArray(vloc); + glDisableVertexAttribArray(nloc); +} + +void Triangle::draw_wire() const +{ + static const int idxarr[] = {0, 1, 1, 2, 2, 0}; + int vloc = Mesh::get_attrib_location(MESH_ATTR_VERTEX); + + glEnableVertexAttribArray(vloc); + glVertexAttribPointer(vloc, 3, GL_FLOAT, GL_FALSE, 0, &v[0].x); + + glDrawElements(GL_LINES, 6, GL_UNSIGNED_INT, idxarr); + + glDisableVertexAttribArray(vloc); +} + +Vec3 Triangle::calc_barycentric(const Vec3 &pos) const +{ + Vec3 norm = get_normal(); + + float area_sq = fabs(dot(cross(v[1] - v[0], v[2] - v[0]), norm)); + if(area_sq < 1e-5) { + return Vec3(0, 0, 0); + } + + float asq0 = fabs(dot(cross(v[1] - pos, v[2] - pos), norm)); + float asq1 = fabs(dot(cross(v[2] - pos, v[0] - pos), norm)); + float asq2 = fabs(dot(cross(v[0] - pos, v[1] - pos), norm)); + + return Vec3(asq0 / area_sq, asq1 / area_sq, asq2 / area_sq); +} + +bool Triangle::intersect(const Ray &ray, HitPoint *hit) const +{ + Vec3 normal = get_normal(); + + float ndotdir = dot(ray.dir, normal); + if(fabs(ndotdir) < 1e-4) { + return false; + } + + Vec3 vertdir = v[0] - ray.origin; + float t = dot(normal, vertdir) / ndotdir; + + Vec3 pos = ray.origin + ray.dir * t; + Vec3 bary = calc_barycentric(pos); + + if(bary.x + bary.y + bary.z > 1.00001) { + return false; + } + + if(hit) { + hit->dist = t; + hit->pos = ray.origin + ray.dir * t; + hit->normal = normal; + hit->data = (void*)this; + } + return true; +} diff --git a/src/mesh.h b/src/mesh.h new file mode 100644 index 0000000..90cd1e7 --- /dev/null +++ b/src/mesh.h @@ -0,0 +1,248 @@ +#ifndef MESH_H_ +#define MESH_H_ + +#include +#include +#include +#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 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 bones; // bones affecting this mesh + + // index data and buffer object + std::vector idata; + unsigned int ibo; + mutable bool ibo_valid; + mutable bool idata_valid; + + // index buffer object for wireframe rendering (constructed on demand) + unsigned int wire_ibo; + mutable bool wire_ibo_valid; + + // axis-aligned bounding box + mutable AABox aabb; + mutable bool aabb_valid; + + // bounding sphere + mutable Sphere bsph; + mutable bool bsph_valid; + + // keeps the last intersected face + mutable Triangle hitface; + // keeps the last intersected vertex position + mutable Vec3 hitvert; + + void calc_aabb(); + void calc_bsph(); + + static unsigned int intersect_mode; + static float vertex_sel_dist; + + static float vis_vecsize; + + /// update the VBOs after data has changed (invalid vbo/ibo) + void update_buffers(); + /// construct/update the wireframe index buffer (called from draw_wire). + void update_wire_ibo(); + + mutable int cur_sdr; + bool pre_draw() const; + void post_draw() const; + + +public: + static bool use_custom_sdr_attr; + + Mesh(); + ~Mesh(); + + Mesh(const Mesh &rhs); + Mesh &operator =(const Mesh &rhs); + bool clone(const Mesh &m); + + void set_name(const char *name); + const char *get_name() const; + + bool has_attrib(int attr) const; + bool is_indexed() const; + + // clears everything about this mesh, and returns to the newly constructed state + void clear(); + + // access the vertex attribute data + // if vdata == 0, space is just allocated + float *set_attrib_data(int attrib, int nelem, unsigned int num, const float *vdata = 0); // invalidates vbo + float *get_attrib_data(int attrib); // invalidates vbo + const float *get_attrib_data(int attrib) const; + + // simple access to any particular attribute + void set_attrib(int attrib, int idx, const Vec4 &v); // invalidates vbo + Vec4 get_attrib(int attrib, int idx) const; + + int get_attrib_count(int attrib) const; + + // ... same for index data + unsigned int *set_index_data(int num, const unsigned int *indices = 0); // invalidates ibo + unsigned int *get_index_data(); // invalidates ibo + const unsigned int *get_index_data() const; + + int get_index_count() const; + + void append(const Mesh &mesh); + + // immediate-mode style mesh construction interface + void vertex(float x, float y, float z); + void normal(float nx, float ny, float nz); + void tangent(float tx, float ty, float tz); + void texcoord(float u, float v, float w); + void boneweights(float w1, float w2, float w3, float w4); + void boneidx(int idx1, int idx2, int idx3, int idx4); + + int get_poly_count() const; + + /* apply a transformation to the vertices and its inverse-transpose + * to the normals and tangents. + */ + void apply_xform(const Mat4 &xform); + void apply_xform(const Mat4 &xform, const Mat4 &dir_xform); + + void flip(); // both faces and normals + void flip_faces(); + void flip_normals(); + + void explode(); // undo all vertex sharing + + void calc_face_normals(); // this is only guaranteed to work on an exploded mesh + + // adds a bone and returns its index + /*int add_bone(XFormNode *bone); + const XFormNode *get_bone(int idx) const; + int get_bones_count() const;*/ + + // access the shader attribute locations + static void set_attrib_location(int attr, int loc); + static int get_attrib_location(int attr); + static void clear_attrib_locations(); + + static void set_vis_vecsize(float sz); + static float get_vis_vecsize(); + + void draw() const; + void draw_wire() const; + void draw_vertices() const; + void draw_normals() const; + void draw_tangents() const; + + /** get the bounding box in local space. The result will be cached, and subsequent + * calls will return the same box. The cache gets invalidated by any functions that can affect + * the vertex data (non-const variant of get_attrib_data(MESH_ATTR_VERTEX, ...) included). + * @{ */ + void get_aabbox(Vec3 *vmin, Vec3 *vmax) const; + const AABox &get_aabbox() const; + /// @} + + /** get the bounding sphere in local space. The result will be cached, and subsequent + * calls will return the same box. The cache gets invalidated by any functions that can affect + * the vertex data (non-const variant of get_attrib_data(MESH_ATTR_VERTEX, ...) included). + * @{ */ + float get_bsphere(Vec3 *center, float *rad) const; + const Sphere &get_bsphere() const; + + static void set_intersect_mode(unsigned int mode); + static unsigned int get_intersect_mode(); + static void set_vertex_select_distance(float dist); + static float get_vertex_select_distance(); + + /** Find the intersection between the mesh and a ray. + * XXX Brute force at the moment, not intended to be used for anything other than picking in tools. + * If you intend to use it in a speed-critical part of the code, you'll *have* to optimize it! + */ + bool intersect(const Ray &ray, HitPoint *hit = 0) const; + + // texture coordinate manipulation + void texcoord_apply_xform(const Mat4 &xform); + void texcoord_gen_plane(const Vec3 &norm, const Vec3 &tang); + void texcoord_gen_box(); + void texcoord_gen_cylinder(); + + bool dump(const char *fname) const; + bool dump(FILE *fp) const; + bool dump_obj(const char *fname) const; + bool dump_obj(FILE *fp) const; +}; + +#endif // MESH_H_ diff --git a/src/meshgen.cc b/src/meshgen.cc new file mode 100644 index 0000000..7664aea --- /dev/null +++ b/src/meshgen.cc @@ -0,0 +1,883 @@ +#include +#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 *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 verts; + for(int i=0; iclear(); + 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; iclear(); + 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; iclear(); + 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; iclear(); + 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; iclear(); + + 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; iclear(); + + 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; iclear(); + + 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 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 0.5 ? v - dv * 0.25 : v + dv * 0.25; + nextu = sweep_vert(fmod(u + du, 1.0), new_v, height, sfunc, cls); + tang = nextu - pos; + } + + Vec3 normal; + Vec3 nextv = sweep_vert(u, v + dv, height, sfunc, cls); + Vec3 bitan = nextv - pos; + if(length_sq(bitan) < 1e-6) { + nextv = sweep_vert(u, v - dv, height, sfunc, cls); + bitan = pos - nextv; + } + + normal = cross(tang, bitan); + + *varr++ = pos; + *narr++ = normalize(normal); + *tarr++ = normalize(tang); + *uvarr++ = Vec2(u, v); + + if(i < usub && j < vsub) { + int idx = i * vverts + j; + + *idxarr++ = idx; + *idxarr++ = idx + vverts + 1; + *idxarr++ = idx + 1; + + *idxarr++ = idx; + *idxarr++ = idx + vverts; + *idxarr++ = idx + vverts + 1; + } + + v += dv; + } + u += du; + } +} diff --git a/src/meshgen.h b/src/meshgen.h new file mode 100644 index 0000000..f154c24 --- /dev/null +++ b/src/meshgen.h @@ -0,0 +1,23 @@ +#ifndef MESHGEN_H_ +#define MESHGEN_H_ + +#include "gmath/gmath.h" + +class Mesh; + +void gen_sphere(Mesh *mesh, float rad, int usub, int vsub, float urange = 1.0, float vrange = 1.0); +void gen_geosphere(Mesh *mesh, float rad, int subdiv, bool hemi = false); +void gen_torus(Mesh *mesh, float mainrad, float ringrad, int usub, int vsub, float urange = 1.0, float vrange = 1.0); +void gen_cylinder(Mesh *mesh, float rad, float height, int usub, int vsub, int capsub = 0, float urange = 1.0, float vrange = 1.0); +void gen_cone(Mesh *mesh, float rad, float height, int usub, int vsub, int capsub = 0, float urange = 1.0, float vrange = 1.0); +void gen_plane(Mesh *mesh, float width, float height, int usub = 1, int vsub = 1); +void gen_heightmap(Mesh *mesh, float width, float height, int usub, int vsub, float (*hf)(float, float, void*), void *hfdata = 0); +void gen_box(Mesh *mesh, float xsz, float ysz, float zsz, int usub = 1, int vsub = 1); + +void gen_revol(Mesh *mesh, int usub, int vsub, Vec2 (*rfunc)(float, float, void*), void *cls = 0); +void gen_revol(Mesh *mesh, int usub, int vsub, Vec2 (*rfunc)(float, float, void*), Vec2 (*nfunc)(float, float, void*), void *cls); + +/* callback args: (float u, float v, void *cls) -> Vec2 XZ offset u,v in [0, 1] */ +void gen_sweep(Mesh *mesh, float height, int usub, int vsub, Vec2 (*sfunc)(float, float, void*), void *cls = 0); + +#endif // MESHGEN_H_ diff --git a/src/metascene.cc b/src/metascene.cc new file mode 100644 index 0000000..e0b2b20 --- /dev/null +++ b/src/metascene.cc @@ -0,0 +1,353 @@ +#include +#include +#include "metascene.h" +#include "scene.h" +#include "treestore.h" +#include "logger.h" +#include "app.h" + +#ifdef WIN32 +#include +#else +#include +#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; iupdate(dt); + } +} + +void MetaScene::draw() const +{ + int nscn = scenes.size(); + for(int i=0; idraw(); + } +} + +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 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; imeshes[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; imeshes[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; imtledit[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; iobjects[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; iget_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; iget_num_children(); i++) { + print_scene_graph(n->get_child(i), level + 1); + } +} diff --git a/src/metascene.h b/src/metascene.h new file mode 100644 index 0000000..8d0f5c2 --- /dev/null +++ b/src/metascene.h @@ -0,0 +1,32 @@ +#ifndef METASCENE_H_ +#define METASCENE_H_ + +#include +#include "scene.h" +#include "mesh.h" +#include "datamap.h" + +class MetaScene { +public: + DataMap datamap; + + std::vector scenes; + + Mesh *walk_mesh; + Vec3 start_pos; + Quat start_rot; + + std::map scndata; + + + MetaScene(); + ~MetaScene(); + + bool load(const char *fname); + bool scene_loaded(Scene *scn); + + void update(float dt); + void draw() const; +}; + +#endif // METASCENE_H_ diff --git a/src/object.cc b/src/object.cc new file mode 100644 index 0000000..d850d47 --- /dev/null +++ b/src/object.cc @@ -0,0 +1,37 @@ +#include "object.h" +#include "snode.h" + + +Object::Object() +{ + name = ""; + node = 0; +} + +ObjType Object::get_type() const +{ + return OBJ_NULL; +} + +void Object::set_name(const char *name) +{ + this->name = name; +} + +const char *Object::get_name() const +{ + return name.c_str(); +} + +bool Object::intersect(const Ray &ray, HitPoint *hit) const +{ + return false; +} + +void Object::update(float dt) +{ +} + +void Object::draw() const +{ +} diff --git a/src/object.h b/src/object.h new file mode 100644 index 0000000..cc82d1b --- /dev/null +++ b/src/object.h @@ -0,0 +1,37 @@ +#ifndef OBJECT_H_ +#define OBJECT_H_ + +#include +#include +#include "geom.h" +#include "material.h" + +class Object; +class SceneNode; + +enum ObjType { OBJ_NULL, OBJ_MESH }; + +class Object { +private: + std::string name; + +public: + Material mtl; + //GeomObject *bvol; + SceneNode *node; + + Object(); + virtual ~Object() = default; + + virtual ObjType get_type() const; + + virtual void set_name(const char *name); + virtual const char *get_name() const; + + virtual bool intersect(const Ray &ray, HitPoint *hit = 0) const; + + virtual void update(float dt = 0.0f); + virtual void draw() const; +}; + +#endif // OBJECT_H_ diff --git a/src/objmesh.cc b/src/objmesh.cc new file mode 100644 index 0000000..3076e67 --- /dev/null +++ b/src/objmesh.cc @@ -0,0 +1,33 @@ +#include "opengl.h" +#include "objmesh.h" +#include "snode.h" + +ObjMesh::ObjMesh() +{ + mesh = 0; +} + +ObjType ObjMesh::get_type() const +{ + return OBJ_MESH; +} + +void ObjMesh::draw() const +{ + if(!mesh) return; + + mtl.setup(); + + if(node) { + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glMultMatrixf(node->get_matrix()[0]); + } + + mesh->draw(); + + if(node) { + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + } +} diff --git a/src/objmesh.h b/src/objmesh.h new file mode 100644 index 0000000..811caf6 --- /dev/null +++ b/src/objmesh.h @@ -0,0 +1,18 @@ +#ifndef OBJMESH_H_ +#define OBJMESH_H_ + +#include "object.h" +#include "mesh.h" + +class ObjMesh : public Object { +public: + Mesh *mesh; + + ObjMesh(); + + ObjType get_type() const; + + void draw() const; +}; + +#endif // OBJMESH_H_ diff --git a/src/opengl.h b/src/opengl.h new file mode 100644 index 0000000..deeddd3 --- /dev/null +++ b/src/opengl.h @@ -0,0 +1,6 @@ +#ifndef OPENGL_H_ +#define OPENGL_H_ + +#include + +#endif // OPENGL_H_ diff --git a/src/opt.cc b/src/opt.cc new file mode 100644 index 0000000..18d73f2 --- /dev/null +++ b/src/opt.cc @@ -0,0 +1,114 @@ +#include +#include +#include +#include +#include "opt.h" + +Options opt; + +Options def_opt = { + 1280, 800, + false, // vr + false, // fullscreen + 0 // scene file +}; + +enum { + OPT_SIZE, + OPT_VR, + OPT_FULLSCREEN, + OPT_WINDOWED, + OPT_SCENEFILE, + OPT_HELP +}; + +static optcfg_option options[] = { + // short, long, id, desc + {'s', "size", OPT_SIZE, "window size (WxH)"}, + {0, "vr", OPT_VR, "enable VR mode"}, + {'f', "fullscreen", OPT_FULLSCREEN, "run in fullscreen mode"}, + {'w', "windowed", OPT_WINDOWED, "run in windowed mode"}, + {0, "scene", OPT_SCENEFILE, "scene file to open"}, + {'h', "help", OPT_HELP, "print usage and exit"}, + OPTCFG_OPTIONS_END +}; + +static int opt_handler(optcfg *oc, int opt, void *cls); +static int arg_handler(optcfg *oc, const char *arg, void *cls); + +bool init_options(int argc, char **argv, const char *cfgfile) +{ + // default options + opt = def_opt; + + optcfg *oc = optcfg_init(options); + optcfg_set_opt_callback(oc, opt_handler, 0); + optcfg_set_arg_callback(oc, arg_handler, 0); + + if(cfgfile) { + optcfg_parse_config_file(oc, cfgfile); + } + + if(argv && optcfg_parse_args(oc, argc, argv) == -1) { + fprintf(stderr, "invalid option\n"); + optcfg_destroy(oc); + return false; + } + + optcfg_destroy(oc); + return true; +} + +static bool is_enabled(optcfg *oc) +{ + int res; + optcfg_enabled_value(oc, &res); + return res != 0; +} + +static int opt_handler(optcfg *oc, int optid, void *cls) +{ + switch(optid) { + case OPT_SIZE: + { + char *valstr = optcfg_next_value(oc); + if(!valstr || sscanf(valstr, "%dx%d", &opt.width, &opt.height) != 2) { + fprintf(stderr, "size must be in the form: WIDTHxHEIGHT\n"); + return -1; + } + } + break; + + case OPT_VR: + opt.vr = is_enabled(oc); + break; + + case OPT_FULLSCREEN: + opt.fullscreen = is_enabled(oc); + break; + + case OPT_WINDOWED: + opt.fullscreen = !is_enabled(oc); + break; + + case OPT_SCENEFILE: + opt.scenefile = strdup(optcfg_next_value(oc)); + break; + + case OPT_HELP: + printf("Usage: vrfileman [options]\nOptions:\n"); + optcfg_print_options(oc); + exit(0); + } + return 0; +} + +static int arg_handler(optcfg *oc, const char *arg, void *cls) +{ + if(opt.scenefile) { + fprintf(stderr, "unexpected argument: %s\n", arg); + return -1; + } + opt.scenefile = arg; + return 0; +} diff --git a/src/opt.h b/src/opt.h new file mode 100644 index 0000000..e173545 --- /dev/null +++ b/src/opt.h @@ -0,0 +1,15 @@ +#ifndef OPT_H_ +#define OPT_H_ + +struct Options { + int width, height; + bool vr; + bool fullscreen; + const char *scenefile; +}; + +extern Options opt, def_opt; + +bool init_options(int argc, char **argv, const char *cfgfile); + +#endif // OPT_H_ diff --git a/src/post.cc b/src/post.cc new file mode 100644 index 0000000..b675f5b --- /dev/null +++ b/src/post.cc @@ -0,0 +1,67 @@ +#include "opengl.h" +#include "app.h" + +static void update_fbtex(); + +static unsigned int fb_tex; +static int fb_tex_width, fb_tex_height; + +void slow_post(unsigned int sdr) +{ + update_fbtex(); + + glBindTexture(GL_TEXTURE_2D, fb_tex); + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, win_width, win_height); + + glPushAttrib(GL_ENABLE_BIT); + glDisable(GL_DEPTH_TEST); + glDisable(GL_LIGHTING); + + glUseProgram(sdr); + + float umax = (float)win_width / (float)fb_tex_width; + float vmax = (float)win_height / (float)fb_tex_height; + + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(-1, -1); + glTexCoord2f(umax, 0); + glVertex2f(1, -1); + glTexCoord2f(umax, vmax); + glVertex2f(1, 1); + glTexCoord2f(0, vmax); + glVertex2f(-1, 1); + glEnd(); + + glPopAttrib(); +} + +static int next_pow2(int x) +{ + x--; + x = (x >> 1) | x; + x = (x >> 2) | x; + x = (x >> 4) | x; + x = (x >> 8) | x; + x = (x >> 16) | x; + return x + 1; +} + +static void update_fbtex() +{ + if(win_width <= fb_tex_width && win_height <= fb_tex_height) { + return; // nothing to do + } + if(!fb_tex) { + glGenTextures(1, &fb_tex); + glBindTexture(GL_TEXTURE_2D, fb_tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } else { + glBindTexture(GL_TEXTURE_2D, fb_tex); + } + + fb_tex_width = next_pow2(win_width); + fb_tex_height = next_pow2(win_height); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, fb_tex_width, fb_tex_height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0); +} diff --git a/src/post.h b/src/post.h new file mode 100644 index 0000000..737cb02 --- /dev/null +++ b/src/post.h @@ -0,0 +1,6 @@ +#ifndef POST_H_ +#define POST_H_ + +void slow_post(unsigned int sdr); + +#endif // POST_H_ diff --git a/src/scene.cc b/src/scene.cc new file mode 100644 index 0000000..683c9ec --- /dev/null +++ b/src/scene.cc @@ -0,0 +1,254 @@ +#include +#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; iget_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; imeshes[i]); + } + scn->meshes.clear(); + + int nobj = scn->objects.size(); + for(int i=0; iobjects[i]); + } + scn->objects.clear(); + + if(nodes) { + int nchildren = scn->nodes ? scn->nodes->get_num_children() : 0; + for(int i=0; inodes->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::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::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; iget_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 *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; iget_child(i), re); + } +} + +Scene *Scene::extract_nodes(const char *qstr) +{ + if(!nodes) return 0; + + std::regex re{qstr}; + + std::list 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; iget_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; inode) { + // 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; idraw(); + } + } else { + int nmesh = meshes.size(); + for(int i=0; idraw(); + } + } +} diff --git a/src/scene.h b/src/scene.h new file mode 100644 index 0000000..1cf8dcc --- /dev/null +++ b/src/scene.h @@ -0,0 +1,90 @@ +#ifndef SCENE_H_ +#define SCENE_H_ + +#include +#include +#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 meshes; + std::vector 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 { +private: + static Scene *create_scene(); + static bool load_scene(Scene *scn, const char *fname); + static bool done_scene(Scene *scn); + static void free_scene(Scene *scn); + +public: + SceneSet(); +}; + +#endif // SCENE_H_ diff --git a/src/sceneload.cc b/src/sceneload.cc new file mode 100644 index 0000000..e33e457 --- /dev/null +++ b/src/sceneload.cc @@ -0,0 +1,395 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 node_by_name; + std::map 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; imNumMeshes; 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; imRootNode->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; idatamap); + 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; imNumMeshes; 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; imNumChildren; 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; ixzy(); + ++vptr; + } + + Vec3 *nptr = (Vec3*)mesh->get_attrib_data(MESH_ATTR_NORMAL); + for(int i=0; ixzy(); + ++nptr; + } + + Vec3 *tptr = (Vec3*)mesh->get_attrib_data(MESH_ATTR_TANGENT); + for(int i=0; ixzy(); + ++tptr; + } + } + + unsigned int *iptr = mesh->set_index_data(num_faces * 3); + for(int i=0; imFaces[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(create_scene, load_scene, done_scene, free_scene) +{ +} + +Scene *SceneSet::create_scene() +{ + return new Scene; +} + +bool SceneSet::load_scene(Scene *scn, const char *fname) +{ + return scn->load(fname, SCNLOAD_FLIPTEX | SCNLOAD_STAGE_IO); +} + +bool SceneSet::done_scene(Scene *scn) +{ + bool res = scn->load(0, SCNLOAD_STAGE_GL); + if(scn->metascn) { + scn->metascn->scene_loaded(scn); + } + return res; +} + +void SceneSet::free_scene(Scene *scn) +{ + delete scn; +} diff --git a/src/sceneload.h b/src/sceneload.h new file mode 100644 index 0000000..2af093d --- /dev/null +++ b/src/sceneload.h @@ -0,0 +1,8 @@ +#ifndef SCENELOAD_H_ +#define SCENELOAD_H_ + +#include "scene.h" + +bool load_scene( + +#endif // SCENELOAD_H_ diff --git a/src/sdr.c b/src/sdr.c new file mode 100644 index 0000000..6b06991 --- /dev/null +++ b/src/sdr.c @@ -0,0 +1,563 @@ +#include +#include +#include +#include +#include +#include +#include "opengl.h" +#include "logger.h" + +#if defined(unix) || defined(__unix__) +#include +#include +#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"; +} + +static int sdrtypeidx(unsigned int sdrtype) +{ + switch(sdrtype) { + case GL_VERTEX_SHADER: + return 0; + case GL_FRAGMENT_SHADER: + return 1; + case GL_TESS_CONTROL_SHADER: + return 2; + case GL_TESS_EVALUATION_SHADER: + return 3; + case GL_GEOMETRY_SHADER: + return 4; + default: + break; + } + return 0; +} diff --git a/src/sdr.h b/src/sdr.h new file mode 100644 index 0000000..7bf2389 --- /dev/null +++ b/src/sdr.h @@ -0,0 +1,68 @@ +#ifndef SDR_H_ +#define SDR_H_ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* ---- shaders ---- */ +unsigned int create_vertex_shader(const char *src); +unsigned int create_pixel_shader(const char *src); +unsigned int create_tessctl_shader(const char *src); +unsigned int create_tesseval_shader(const char *src); +unsigned int create_geometry_shader(const char *src); +unsigned int create_shader(const char *src, unsigned int sdr_type); +void free_shader(unsigned int sdr); + +unsigned int load_vertex_shader(const char *fname); +unsigned int load_pixel_shader(const char *fname); +unsigned int load_tessctl_shader(const char *fname); +unsigned int load_tesseval_shader(const char *fname); +unsigned int load_geometry_shader(const char *fname); +unsigned int load_shader(const char *src, unsigned int sdr_type); + +int add_shader(const char *fname, unsigned int sdr); +int remove_shader(const char *fname); + +/* ---- gpu programs ---- */ +unsigned int create_program(void); +unsigned int create_program_link(unsigned int sdr0, ...); +unsigned int create_program_load(const char *vfile, const char *pfile); +void free_program(unsigned int sdr); + +void attach_shader(unsigned int prog, unsigned int sdr); +int link_program(unsigned int prog); +int bind_program(unsigned int prog); + +int get_uniform_loc(unsigned int prog, const char *name); + +int set_uniform_int(unsigned int prog, const char *name, int val); +int set_uniform_float(unsigned int prog, const char *name, float val); +int set_uniform_float2(unsigned int prog, const char *name, float x, float y); +int set_uniform_float3(unsigned int prog, const char *name, float x, float y, float z); +int set_uniform_float4(unsigned int prog, const char *name, float x, float y, float z, float w); +int set_uniform_matrix4(unsigned int prog, const char *name, const float *mat); +int set_uniform_matrix4_transposed(unsigned int prog, const char *name, const float *mat); + +int get_attrib_loc(unsigned int prog, const char *name); +void set_attrib_float3(int attr_loc, float x, float y, float z); + +/* ---- shader composition ---- */ + +/* clear shader header/footer text. + * pass the shader type to clear, or 0 to clear all types */ +void clear_shader_header(unsigned int type); +void clear_shader_footer(unsigned int type); +/* append text to the header/footer of a specific shader type + * or use type 0 to add it to all shade types */ +void add_shader_header(unsigned int type, const char *s); +void add_shader_footer(unsigned int type, const char *s); +/* get the current header/footer text for a specific shader type */ +const char *get_shader_header(unsigned int type); +const char *get_shader_footer(unsigned int type); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SDR_H_ */ diff --git a/src/shadow.cc b/src/shadow.cc new file mode 100644 index 0000000..357ed82 --- /dev/null +++ b/src/shadow.cc @@ -0,0 +1,147 @@ +#define GPH_NAMESPACE +#include +#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; +} diff --git a/src/shadow.h b/src/shadow.h new file mode 100644 index 0000000..b40b6f2 --- /dev/null +++ b/src/shadow.h @@ -0,0 +1,17 @@ +#ifndef SHADOW_H_ +#define SHADOW_H_ + +#include + +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_ diff --git a/src/snode.cc b/src/snode.cc new file mode 100644 index 0000000..ffb1d08 --- /dev/null +++ b/src/snode.cc @@ -0,0 +1,263 @@ +#include +#include +#include +#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; iupdate(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; iapply_xform(); + } + + int nobj = obj.size(); + for(int i=0; iget_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; iintersect(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; iintersect(ray, hit)) { + if(!hit) return true; + if(hit->dist < nearest.dist) { + nearest = *hit; + } + } + } + + if(nearest.dist < FLT_MAX) { + *hit = nearest; + hit->ray = ray; + return true; + } + return false; +} diff --git a/src/snode.h b/src/snode.h new file mode 100644 index 0000000..5af02f4 --- /dev/null +++ b/src/snode.h @@ -0,0 +1,74 @@ +#ifndef SNODE_H_ +#define SNODE_H_ + +#include +#include "object.h" +#include "gmath/gmath.h" + +class Scene; + +class SceneNode { +private: + char *name; + + Vec3 pos; + Quat rot; + Vec3 scale; + + std::vector obj; + + SceneNode *parent; + std::vector children; + + Mat4 xform; + Mat4 inv_xform; + +public: + Scene *scene; // scene to which this node belongs + Mat4 dbg_xform; + + SceneNode(); + explicit SceneNode(Object *obj); + ~SceneNode(); + + void set_name(const char *s); + const char *get_name() const; + + void add_child(SceneNode *node); + bool remove_child(SceneNode *node); + + int get_num_children() const; + SceneNode *get_child(int idx) const; + + SceneNode *get_parent() const; + + void add_object(Object *obj); + bool remove_object(Object *obj); + + int get_num_objects() const; + Object *get_object(int idx) const; + + void set_position(const Vec3 &pos); + void set_rotation(const Quat &rot); + void set_scaling(const Vec3 &scale); + + const Vec3 &get_node_position() const; + const Quat &get_node_rotation() const; + const Vec3 &get_node_scaling() const; + + Vec3 get_position() const; + Quat get_rotation() const; + Vec3 get_scaling() const; + + const Mat4 &get_matrix() const; + const Mat4 &get_inv_matrix() const; + + void update_node(float dt = 0.0f); + void update(float dt = 0.0f); + + void apply_xform(); + + bool intersect(const Ray &ray, HitPoint *hit) const; +}; + +#endif // SNODE_H_ diff --git a/src/texture.cc b/src/texture.cc new file mode 100644 index 0000000..ff4d1b5 --- /dev/null +++ b/src/texture.cc @@ -0,0 +1,538 @@ +#include +#include +#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> 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(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::const_iterator iter = data.find(fname); + if(iter != data.end()) { + return iter->second; + } + + Texture *res = create(); + data[fname] = res; + res->create_default(type); + resman_lookup(rman, fname, res); + return res; +} + +// static callbacks + +Texture *TextureSet::create_tex() +{ + return new Texture; +} + +bool TextureSet::load_tex(Texture *tex, const char *fname) +{ + Image *img = new Image; + img->name = fname; + if(!img->load(fname)) { + delete img; + return false; + } + + delete tex->img; + tex->img = img; + + return true; +} + +bool TextureSet::done_tex(Texture *tex) +{ + //debug_log("TextureSet::done_tex [%s]\n", tex->img->name.c_str()); + if(!tex->img) { + return false; + } + + tex->set_image(*tex->img); + return true; +} + +void TextureSet::free_tex(Texture *tex) +{ + delete tex; +} diff --git a/src/texture.h b/src/texture.h new file mode 100644 index 0000000..5e18e7a --- /dev/null +++ b/src/texture.h @@ -0,0 +1,75 @@ +#ifndef TEXTURE_H_ +#define TEXTURE_H_ + +#include "dataset.h" +#include "datamap.h" +#include "opengl.h" + +class Image; + +enum TextureType { TEX_2D, TEX_CUBE }; + +class Texture { +private: + unsigned int id; + unsigned int target; + unsigned int texfmt; + int sz[3]; + Image *img; + static Image *default_img; + + Texture(const Texture &tex) {} + Texture &operator =(const Texture &tex) { return *this; } + + void set_image_2d(const Image &img); + bool set_image_cube(const Image &img, int idx); + bool set_image_cube(const Image &img); + + bool load_cube(const char *fname); + + /* for loading multiple cubemap faces from a single image */ + bool set_cube_multi(const Image &img, const float *xoffsets, const float *yoffsets, float sz, + unsigned int rotmask = 0); + +public: + Texture(); + ~Texture(); + + void set_wrapping(unsigned int wrap); + void set_filtering(unsigned int filt); + void set_filtering(unsigned int min_filt, unsigned int mag_filt); + + unsigned int get_format() const; + + int get_size(int dim) const; + + void create(int xsz, int ysz, TextureType type = TEX_2D, unsigned int ifmt = GL_RGBA); + void create_default(TextureType type = TEX_2D); + void set_image(const Image &img, int idx = -1); + + bool load(const char *fname); + + unsigned int get_id() const; + TextureType get_type() const; + + void bind(int tex_unit = 0) const; + + friend class TextureSet; +}; + +void bind_texture(Texture *tex, int tunit = 0); + +class TextureSet : public DataSet { +private: + static Texture *create_tex(); + static bool load_tex(Texture *tex, const char *fname); + static bool done_tex(Texture *tex); + static void free_tex(Texture *tex); + +public: + TextureSet(); + + Texture *get_texture(const char *name, TextureType type = TEX_2D, const DataMap *dmap = 0) const; +}; + +#endif // TEXTURE_H_ diff --git a/src/ui.cc b/src/ui.cc new file mode 100644 index 0000000..a0babe4 --- /dev/null +++ b/src/ui.cc @@ -0,0 +1,185 @@ +#include +#include +#include +#include +#include +#include "opengl.h" +#include "ui.h" +#include "app.h" + +#define FONTSZ 16 + +static bool init(); + +struct Message { + long start_time, show_until; + char *str; + Vec3 color; + Message *next; +}; +static Message *msglist; + +struct Text { + char *str; + Vec2 pos; + Vec3 color; + Text *next; +}; +static Text *txlist; + +static long timeout = 2000; +static long trans_time = 250; +static dtx_font *font; + +void set_message_timeout(long tm) +{ + timeout = tm; +} + +void show_message(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + show_messagev(timeout, Vec3(1, 1, 1), fmt, ap); + va_end(ap); +} + +void show_message(long timeout, const Vec3 &color, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + show_messagev(timeout, color, fmt, ap); + va_end(ap); +} + +void show_messagev(long timeout, const Vec3 &color, const char *fmt, va_list ap) +{ + char buf[512]; + + init(); + + vsnprintf(buf, sizeof buf, fmt, ap); + + Message *msg = new Message; + int len = strlen(buf); + msg->str = new char[len + 1]; + memcpy(msg->str, buf, len + 1); + msg->start_time = time_msec; + msg->show_until = time_msec + timeout; + msg->color = color; + + Message dummy; + dummy.next = msglist; + Message *prev = &dummy; + while(prev->next && prev->next->show_until < msg->show_until) { + prev = prev->next; + } + msg->next = prev->next; + prev->next = msg; + msglist = dummy.next; +} + +void print_text(const Vec2 &pos, const Vec3 &color, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + print_textv(pos, color, fmt, ap); + va_end(ap); +} + +void print_textv(const Vec2 &pos, const Vec3 &color, const char *fmt, va_list ap) +{ + char buf[512]; + + init(); + + vsnprintf(buf, sizeof buf, fmt, ap); + + Text *tx = new Text; + int len = strlen(buf); + tx->str = new char[len + 1]; + memcpy(tx->str, buf, len + 1); + tx->color = color; + tx->pos = Vec2(pos.x, -pos.y); + + tx->next = txlist; + txlist = tx; +} + +void draw_ui() +{ + if(!font) return; + + while(msglist && msglist->show_until <= time_msec) { + Message *msg = msglist; + msglist = msg->next; + delete [] msg->str; + delete msg; + } + + dtx_use_font(font, FONTSZ); + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(0, win_width, -win_height, 0, -1, 1); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + glPushAttrib(GL_ENABLE_BIT); + glDisable(GL_LIGHTING); + glDisable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glUseProgram(0); + + Message *msg = msglist; + while(msg) { + long t = time_msec - msg->start_time; + long dur = msg->show_until - msg->start_time; + float alpha = smoothstep(0, trans_time, t) * + (1.0 - smoothstep(dur - trans_time, dur, t)); + glColor4f(msg->color.x, msg->color.y, msg->color.z, alpha); + glTranslatef(0, -dtx_line_height(), 0); + dtx_string(msg->str); + msg = msg->next; + } + + while(txlist) { + Text *tx = txlist; + txlist = txlist->next; + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glTranslatef(tx->pos.x, tx->pos.y, 0); + + glColor3f(tx->color.x, tx->color.y, tx->color.z); + dtx_string(tx->str); + + delete [] tx->str; + delete tx; + } + + glPopAttrib(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); +} + +static bool init() +{ + static bool done_init; + if(done_init) return true; + + done_init = true; + + if(!(font = dtx_open_font("data/ui.font", 0))) { + fprintf(stderr, "failed to open font: data/ui.font\n"); + return false; + } + dtx_prepare_range(font, FONTSZ, 32, 127); + return true; +} diff --git a/src/ui.h b/src/ui.h new file mode 100644 index 0000000..088fb82 --- /dev/null +++ b/src/ui.h @@ -0,0 +1,17 @@ +#ifndef UI_H_ +#define UI_H_ + +#include +#include + +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_ -- 1.7.10.4