tesselation
authorJohn Tsiombikas <nuclear@member.fsf.org>
Sun, 12 Aug 2018 15:39:22 +0000 (18:39 +0300)
committerJohn Tsiombikas <nuclear@member.fsf.org>
Sun, 12 Aug 2018 15:39:22 +0000 (18:39 +0300)
13 files changed:
sdr/field.p.glsl
sdr/field.te.glsl
sdr/field.v.glsl
src/gamescr.cc
src/geom.cc [new file with mode: 0644]
src/geom.h [new file with mode: 0644]
src/main.cc
src/mesh.cc [new file with mode: 0644]
src/mesh.h [new file with mode: 0644]
src/meshgen.cc [new file with mode: 0644]
src/meshgen.h [new file with mode: 0644]
src/opengl.c
src/opengl.h

index b8888c9..1969702 100644 (file)
@@ -1,12 +1,13 @@
 #version 410 compatibility
 
 #version 410 compatibility
 
-uniform sampler2D gvis_tex, field_tex;
+uniform sampler2D gvis_tex;
+uniform float gvis_scale;
 
 void main()
 {
 
 void main()
 {
-       vec4 gridcol = texture2D(gvis_tex, gl_TexCoord[0].st);
-       float field = -texture2D(field_tex, gl_TexCoord[1].st).x;
+       vec2 uv = gl_TexCoord[0].st * vec2(gvis_scale, gvis_scale);
+       vec4 gridcol = texture2D(gvis_tex, uv);
 
 
-       gl_FragColor.rgb = gridcol.rgb + vec3(field, field, field);
+       gl_FragColor.rgb = gridcol.rgb;
        gl_FragColor.a = 1.0;
 }
        gl_FragColor.a = 1.0;
 }
index cb01515..eaf485d 100644 (file)
@@ -2,21 +2,22 @@
 
 layout(quads, ccw) in;
 
 
 layout(quads, ccw) in;
 
+uniform sampler2D field_tex;
+uniform float field_scale;
+
 void main()
 {
        vec4 v1 = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, gl_TessCoord.x);
        vec4 v2 = mix(gl_in[3].gl_Position, gl_in[2].gl_Position, gl_TessCoord.x);
        vec4 pos = mix(v1, v2, gl_TessCoord.y);
 
 void main()
 {
        vec4 v1 = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, gl_TessCoord.x);
        vec4 v2 = mix(gl_in[3].gl_Position, gl_in[2].gl_Position, gl_TessCoord.x);
        vec4 pos = mix(v1, v2, gl_TessCoord.y);
 
-       vec4 top0 = mix(gl_in[0].gl_TexCoord[0], gl_in[1].gl_TexCoord[0], gl_TessCoord.x);
-       vec4 bot0 = mix(gl_in[3].gl_TexCoord[0], gl_in[2].gl_TexCoord[0], gl_TessCoord.x);
-       vec4 res0 = mix(top0, bot0, gl_TessCoord.y);
+       vec4 top = mix(gl_in[0].gl_TexCoord[0], gl_in[1].gl_TexCoord[0], gl_TessCoord.x);
+       vec4 bot = mix(gl_in[3].gl_TexCoord[0], gl_in[2].gl_TexCoord[0], gl_TessCoord.x);
+       vec4 uv = mix(top, bot, gl_TessCoord.y);
 
 
-       vec4 top1 = mix(gl_in[0].gl_TexCoord[1], gl_in[1].gl_TexCoord[1], gl_TessCoord.x);
-       vec4 bot1 = mix(gl_in[3].gl_TexCoord[1], gl_in[2].gl_TexCoord[1], gl_TessCoord.x);
-       vec4 res1 = mix(top1, bot1, gl_TessCoord.y);
+       float field = -texture2D(field_tex, uv.st).x;
+       pos.y = field * field_scale;
 
        gl_Position = gl_ModelViewProjectionMatrix * pos;
 
        gl_Position = gl_ModelViewProjectionMatrix * pos;
-       gl_TexCoord[0] = res0;
-       gl_TexCoord[1] = res1;
+       gl_TexCoord[0] = uv;
 }
 }
index 26760af..cecfabb 100644 (file)
@@ -4,5 +4,4 @@ void main()
 {
        gl_Position = gl_Vertex;
        gl_TexCoord[0] = gl_MultiTexCoord0;
 {
        gl_Position = gl_Vertex;
        gl_TexCoord[0] = gl_MultiTexCoord0;
-       gl_TexCoord[1] = gl_MultiTexCoord1;
 }
 }
index d51c16a..032b3b9 100644 (file)
@@ -32,6 +32,16 @@ struct Rect {
        int width, height;
 };
 
        int width, height;
 };
 
+struct QuadMesh {
+       Vec3 *v;
+       Vec2 *uv;
+       uint16_t *idx;
+
+       int num_verts, num_idx, num_quads;
+
+       unsigned int vbo_v, vbo_uv, ibo;
+};
+
 #define SIM_DT         0.016
 
 #define GRID_SIZE      2048
 #define SIM_DT         0.016
 
 #define GRID_SIZE      2048
@@ -52,6 +62,10 @@ struct Rect {
 static int pos_to_grid(float x, float y);
 static Vec2 grid_to_pos(int gx, int gy);
 
 static int pos_to_grid(float x, float y);
 static Vec2 grid_to_pos(int gx, int gy);
 
+static void destroy_quadmesh(QuadMesh *m);
+static void draw_quadmesh(const QuadMesh *m, unsigned int prim = GL_QUADS);
+static void gen_quad_plane(QuadMesh *mesh, float width, float height, int usub, int vsub);
+
 static float grid[GRID_SIZE * GRID_SIZE];
 static Particle grid_part[GRID_SIZE * GRID_SIZE];
 static Texture *grid_tex;
 static float grid[GRID_SIZE * GRID_SIZE];
 static Particle grid_part[GRID_SIZE * GRID_SIZE];
 static Texture *grid_tex;
@@ -60,6 +74,10 @@ static std::vector<Emitter*> emitters;
 
 static Texture *gvis_tex;      // texture tile for visualizing a grid
 static unsigned int field_sdr;
 
 static Texture *gvis_tex;      // texture tile for visualizing a grid
 static unsigned int field_sdr;
+static int tess_level = 32;
+static float field_scale = 16.0f;
+
+static QuadMesh field_mesh;
 
 static float cam_theta;
 static float cam_dist = 100.0f;
 
 static float cam_theta;
 static float cam_dist = 100.0f;
@@ -97,18 +115,26 @@ bool GameScreen::init()
        }
        set_uniform_int(field_sdr, "gvis_tex", 0);
        set_uniform_int(field_sdr, "field_tex", 1);
        }
        set_uniform_int(field_sdr, "gvis_tex", 0);
        set_uniform_int(field_sdr, "field_tex", 1);
-       set_uniform_int(field_sdr, "tess_level", 2);
+       set_uniform_float(field_sdr, "gvis_scale", FIELD_SIZE / 32.0f);
+       set_uniform_int(field_sdr, "tess_level", tess_level);
+       set_uniform_float(field_sdr, "field_scale", field_scale);
+
+       gen_quad_plane(&field_mesh, FIELD_SIZE, FIELD_SIZE, 32, 32);
 
        // XXX DBG
        emit_place_pos = Vec2(0, 0);
        emit_place_pending = true;
 
 
        // XXX DBG
        emit_place_pos = Vec2(0, 0);
        emit_place_pending = true;
 
+       assert(glGetError() == GL_NO_ERROR);
        return true;
 }
 
 void GameScreen::destroy()
 {
        return true;
 }
 
 void GameScreen::destroy()
 {
+       free_program(field_sdr);
        delete gvis_tex;
        delete gvis_tex;
+       delete grid_tex;
+       destroy_quadmesh(&field_mesh);
 }
 
 static void calc_contrib_bounds(const Emitter *em, Rect *rect)
 }
 
 static void calc_contrib_bounds(const Emitter *em, Rect *rect)
@@ -242,34 +268,16 @@ void GameScreen::draw()
        bind_texture(grid_tex, 1);
 
        glUseProgram(field_sdr);
        bind_texture(grid_tex, 1);
 
        glUseProgram(field_sdr);
+       assert(glGetError() == GL_NO_ERROR);
 
 
-       float maxu = FIELD_SIZE / 32.0f;
-       float maxv = FIELD_SIZE / 32.0f;
-       float hsz = FIELD_SIZE * 0.5f;
-
-       glBegin(GL_QUADS);
-       glColor3f(1, 1, 1);
-
-       glMultiTexCoord2f(0, 0, 0);
-       glMultiTexCoord2f(1, 0, 0);
-       glVertex3f(-hsz, 0, -hsz);
-
-       glMultiTexCoord2f(0, maxu, 0);
-       glMultiTexCoord2f(1, 1, 0);
-       glVertex3f(hsz, 0, -hsz);
-
-       glMultiTexCoord2f(0, maxu, maxv);
-       glMultiTexCoord2f(1, 1, 1);
-       glVertex3f(hsz, 0, hsz);
-
-       glMultiTexCoord2f(0, 0, maxv);
-       glMultiTexCoord2f(1, 0, 1);
-       glVertex3f(-hsz, 0, hsz);
-       glEnd();
+       glPatchParameteri(GL_PATCH_VERTICES, 4);
+       draw_quadmesh(&field_mesh, GL_PATCHES);
 
        glUseProgram(0);
 
        glPopAttrib();
 
        glUseProgram(0);
 
        glPopAttrib();
+
+       assert(glGetError() == GL_NO_ERROR);
 }
 
 void GameScreen::reshape(int x, int y)
 }
 
 void GameScreen::reshape(int x, int y)
@@ -286,6 +294,39 @@ void GameScreen::keyboard(int key, bool pressed)
                        pop_screen();
                        break;
 
                        pop_screen();
                        break;
 
+               case ']':
+                       if(tess_level < glcaps.max_tess_level) {
+                               tess_level++;
+                               printf("tessellation level: %d\n", tess_level);
+                               set_uniform_int(field_sdr, "tess_level", tess_level);
+                               glUseProgram(0);
+                       }
+                       break;
+
+               case '[':
+                       if(tess_level > 1) {
+                               tess_level--;
+                               printf("tessellation level: %d\n", tess_level);
+                               set_uniform_int(field_sdr, "tess_level", tess_level);
+                               glUseProgram(0);
+                       }
+                       break;
+
+               case '=':
+                       field_scale += 0.5;
+                       printf("field scale: %f\n", field_scale);
+                       set_uniform_float(field_sdr, "field_scale", field_scale);
+                       break;
+
+               case '-':
+                       field_scale -= 0.5;
+                       if(field_scale < 0.0f) {
+                               field_scale = 0.0f;
+                       }
+                       printf("field scale: %f\n", field_scale);
+                       set_uniform_float(field_sdr, "field_scale", field_scale);
+                       break;
+
                default:
                        break;
                }
                default:
                        break;
                }
@@ -340,3 +381,97 @@ static Vec2 grid_to_pos(int gx, int gy)
 
        return Vec2(x, y);
 }
 
        return Vec2(x, y);
 }
+
+static void destroy_quadmesh(QuadMesh *m)
+{
+       glDeleteBuffers(1, &m->vbo_v);
+       glDeleteBuffers(1, &m->vbo_uv);
+       glDeleteBuffers(1, &m->ibo);
+
+       delete [] m->v;
+       delete [] m->uv;
+       delete [] m->idx;
+}
+
+static void draw_quadmesh(const QuadMesh *m, unsigned int prim)
+{
+       glEnableClientState(GL_VERTEX_ARRAY);
+       glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+
+       glBindBuffer(GL_ARRAY_BUFFER, m->vbo_v);
+       glVertexPointer(3, GL_FLOAT, 0, 0);
+
+       glBindBuffer(GL_ARRAY_BUFFER, m->vbo_uv);
+       glTexCoordPointer(2, GL_FLOAT, 0, 0);
+
+       glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+       glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m->ibo);
+       glDrawElements(prim, m->num_idx, GL_UNSIGNED_SHORT, 0);
+       glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+       glDisableClientState(GL_VERTEX_ARRAY);
+       glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+}
+
+static void gen_quad_plane(QuadMesh *m, float width, float height, int usub, int vsub)
+{
+       Vec3 *vptr;
+       Vec2 *uvptr;
+       uint16_t *iptr;
+
+       if(usub < 1) usub = 1;
+       if(vsub < 1) vsub = 1;
+
+       int uverts = usub + 1;
+       int vverts = vsub + 1;
+       m->num_verts = uverts * vverts;
+       m->num_quads = usub * vsub;
+       m->num_idx = m->num_quads * 4;
+
+       vptr = m->v = new Vec3[m->num_verts];
+       uvptr = m->uv = new Vec2[m->num_verts];
+       iptr = m->idx = new uint16_t[m->num_idx];
+
+       float du = 1.0f / (float)usub;
+       float dv = 1.0f / (float)vsub;
+
+       float u = 0.0f;
+       for(int i=0; i<uverts; i++) {
+               float x = (u - 0.5f) * width;
+               float v = 0.0f;
+               for(int j=0; j<vverts; j++) {
+                       float y = (v - 0.5f) * height;
+
+                       *vptr++ = Vec3(x, 0, y);
+                       *uvptr++ = Vec2(u, v);
+
+                       if(i < usub && j < vsub) {
+                               int idx = i * vverts + j;
+
+                               *iptr++ = idx;
+                               *iptr++ = idx + vverts;
+                               *iptr++ = idx + vverts + 1;
+                               *iptr++ = idx + 1;
+                       }
+
+                       v += dv;
+               }
+               u += du;
+       }
+
+       glGenBuffers(1, &m->vbo_v);
+       glBindBuffer(GL_ARRAY_BUFFER, m->vbo_v);
+       glBufferData(GL_ARRAY_BUFFER, m->num_verts * 3 * sizeof(float), m->v, GL_STATIC_DRAW);
+
+       glGenBuffers(1, &m->vbo_uv);
+       glBindBuffer(GL_ARRAY_BUFFER, m->vbo_uv);
+       glBufferData(GL_ARRAY_BUFFER, m->num_verts * 2 * sizeof(float), m->uv, GL_STATIC_DRAW);
+
+       glGenBuffers(1, &m->ibo);
+       glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m->ibo);
+       glBufferData(GL_ELEMENT_ARRAY_BUFFER, m->num_idx * sizeof(uint16_t), m->idx, GL_STATIC_DRAW);
+
+       glBindBuffer(GL_ARRAY_BUFFER, 0);
+       glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+}
diff --git a/src/geom.cc b/src/geom.cc
new file mode 100644 (file)
index 0000000..dfbc52f
--- /dev/null
@@ -0,0 +1,760 @@
+#include <stdlib.h>
+#include <float.h>
+#include <algorithm>
+#include "geom.h"
+
+#define SPHERE(ptr)    ((Sphere*)ptr)
+#define AABOX(ptr)     ((AABox*)ptr)
+#define BOX(ptr)       ((Box*)ptr)
+#define PLANE(ptr)     ((Plane*)ptr)
+
+GeomObject::GeomObject()
+{
+       type = GOBJ_UNKNOWN;
+}
+
+GeomObject::~GeomObject()
+{
+}
+
+bool GeomObject::valid() const
+{
+       return true;
+}
+
+void GeomObject::invalidate()
+{
+}
+
+float GeomObject::distance(const Vec3 &v) const
+{
+       return sqrt(distance_sq(v));
+}
+
+float GeomObject::signed_distance(const Vec3 &v) const
+{
+       return sqrt(signed_distance_sq(v));
+}
+
+Sphere::Sphere()
+{
+       type = GOBJ_SPHERE;
+       radius = 1.0;
+}
+
+Sphere::Sphere(const Vec3 &cent, float radius)
+       : center(cent)
+{
+       type = GOBJ_SPHERE;
+       this->radius = radius;
+}
+
+bool Sphere::valid() const
+{
+       return radius >= 0.0f;
+}
+
+void Sphere::invalidate()
+{
+       center = Vec3(0, 0, 0);
+       radius = -1;
+}
+
+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_sq(const Vec3 &v) const
+{
+       return std::max(length_sq(v - center) - radius * radius, 0.0f);
+}
+
+float Sphere::signed_distance_sq(const Vec3 &v) const
+{
+       return length_sq(v - center) - radius * radius;
+}
+
+AABox::AABox()
+{
+       type = GOBJ_AABOX;
+}
+
+AABox::AABox(const Vec3 &vmin, const Vec3 &vmax)
+       : min(vmin), max(vmax)
+{
+       type = GOBJ_AABOX;
+}
+
+bool AABox::valid() const
+{
+       return min.x <= max.x && min.y <= max.y && min.z <= max.z;
+}
+
+void AABox::invalidate()
+{
+       min = Vec3(FLT_MAX, FLT_MAX, FLT_MAX);
+       max = Vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
+}
+
+Vec3 AABox::get_corner(int idx) const
+{
+       Vec3 v[] = {min, max};
+       static const int xidx[] = {0, 1, 1, 0, 0, 1, 1, 0};
+       static const int yidx[] = {0, 0, 0, 0, 1, 1, 1, 1};
+       static const int zidx[] = {0, 0, 1, 1, 0, 0, 1, 1};
+       return Vec3(v[xidx[idx]].x, v[yidx[idx]].y, v[zidx[idx]].z);
+}
+
+Plane AABox::get_plane(int pidx) const
+{
+       Vec3 c = (max - min) * 0.5f;
+       switch(pidx) {
+       case AABOX_PLANE_PX:
+               return Plane(Vec3(max.x, c.y, c.z), Vec3(1, 0, 0));
+       case AABOX_PLANE_NX:
+               return Plane(Vec3(min.x, c.y, c.z), Vec3(-1, 0, 0));
+       case AABOX_PLANE_PY:
+               return Plane(Vec3(c.x, max.x, c.z), Vec3(0, 1, 0));
+       case AABOX_PLANE_NY:
+               return Plane(Vec3(c.x, min.x, c.z), Vec3(0, -1, 0));
+       case AABOX_PLANE_PZ:
+               return Plane(Vec3(c.x, c.y, max.z), Vec3(0, 0, 1));
+       case AABOX_PLANE_NZ:
+               return Plane(Vec3(c.x, c.y, min.z), Vec3(0, 0, -1));
+       default:
+               break;
+       }
+       abort();
+       return Plane();
+}
+
+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;
+}
+
+#define SQ(x)  ((x) * (x))
+float AABox::distance_sq(const Vec3 &v) const
+{
+       float dsq = 0.0f;
+
+       for(int i=0; i<3; i++) {
+               if(v[i] < min[i]) dsq += SQ(min[i] - v[i]);
+               if(v[i] > max[i]) dsq += SQ(v[i] - max[i]);
+       }
+       return dsq;
+}
+
+float AABox::signed_distance_sq(const Vec3 &v) const
+{
+       if(!contains(v)) {
+               return distance_sq(v);
+       }
+
+       float dsq = 0.0f;
+       for(int i=0; i<3; i++) {
+               float dmin = v[i] - min[i];
+               float dmax = max[i] - v[i];
+               dsq -= dmin < dmax ? SQ(dmin) : SQ(dmax);
+       }
+       return dsq;
+}
+
+Box::Box()
+{
+       type = GOBJ_BOX;
+}
+
+Box::Box(const AABox &aabox, const Mat4 &xform)
+       : xform(xform)
+{
+       type = GOBJ_BOX;
+       min = aabox.min;
+       max = aabox.max;
+}
+
+void Box::invalidate()
+{
+       AABox::invalidate();
+       xform = Mat4::identity;
+}
+
+Box::Box(const Vec3 &min, const Vec3 &max)
+       : AABox(min, max)
+{
+       type = GOBJ_BOX;
+}
+
+Box::Box(const Vec3 &min, const Vec3 &max, const Mat4 &xform)
+       : AABox(min, max), xform(xform)
+{
+       type = GOBJ_BOX;
+}
+
+// XXX all this shit is completely untested
+Box::Box(const Vec3 &pos, const Vec3 &vi, const Vec3 &vj, const Vec3 &vk)
+{
+       type = GOBJ_BOX;
+       float ilen = length(vi);
+       float jlen = length(vj);
+       float klen = length(vk);
+
+       min = Vec3(-ilen, -jlen, -klen);
+       max = Vec3(ilen, jlen, klen);
+
+       float si = ilen == 0.0 ? 1.0 : 1.0 / ilen;
+       float sj = jlen == 0.0 ? 1.0 : 1.0 / jlen;
+       float sk = klen == 0.0 ? 1.0 : 1.0 / klen;
+
+       xform = Mat4(vi * si, vj * sj, vk * sk);
+       xform.translate(pos);
+}
+
+Box::Box(const Vec3 *varr, int vcount)
+{
+       type = GOBJ_BOX;
+       calc_bounding_aabox(this, varr, vcount);
+}
+
+Vec3 Box::get_corner(int idx) const
+{
+       return xform * AABox::get_corner(idx);
+}
+
+bool Box::intersect(const Ray &ray, HitPoint *hit) const
+{
+       Mat4 inv_xform = inverse(xform);
+       Mat4 dir_inv_xform = inv_xform.upper3x3();
+       Mat4 dir_xform = transpose(dir_inv_xform);
+       Ray local_ray = Ray(inv_xform * ray.origin, dir_inv_xform * ray.dir);
+
+       bool res = AABox::intersect(local_ray, hit);
+       if(!res || !hit) return res;
+
+       hit->pos = xform * hit->pos;
+       hit->normal = dir_xform * hit->normal;
+       hit->local_ray = local_ray;
+       hit->ray = ray;
+       return true;
+}
+
+bool Box::contains(const Vec3 &pt) const
+{
+       // XXX is it faster to extract 6 planes and do dot products? sounds marginal
+       return AABox::contains(inverse(xform) * pt);
+}
+
+float Box::distance_sq(const Vec3 &v) const
+{
+       return 0.0f;    // TODO
+}
+
+float Box::signed_distance_sq(const Vec3 &v) const
+{
+       return 0.0f;    // TODO
+}
+
+Plane::Plane()
+       : normal(0.0, 1.0, 0.0)
+{
+       type = GOBJ_PLANE;
+}
+
+Plane::Plane(const Vec3 &p, const Vec3 &norm)
+       : pt(p)
+{
+       type = GOBJ_PLANE;
+       normal = normalize(norm);
+}
+
+Plane::Plane(const Vec3 &p1, const Vec3 &p2, const Vec3 &p3)
+       : pt(p1)
+{
+       type = GOBJ_PLANE;
+       normal = normalize(cross(p2 - p1, p3 - p1));
+}
+
+Plane::Plane(const Vec3 &normal, float dist)
+{
+       type = GOBJ_PLANE;
+       this->normal = normalize(normal);
+       pt = this->normal * dist;
+}
+
+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 std::max(dot(v - pt, normal), 0.0f);
+}
+
+float Plane::signed_distance(const Vec3 &v) const
+{
+       return dot(v - pt, normal);
+}
+
+float Plane::distance_sq(const Vec3 &v) const
+{
+       float d = distance(v);
+       return d * d;
+}
+
+float Plane::signed_distance_sq(const Vec3 &v) const
+{
+       float d = distance(v);
+       return dot(v, normal) >= 0.0f ? d * d : -(d * d);
+}
+
+
+Disc::Disc()
+{
+       type = GOBJ_DISC;
+       radius = 1.0;
+}
+
+Disc::Disc(const Vec3 &pt, const Vec3 &normal, float rad)
+       : Plane(pt, normal)
+{
+       type = GOBJ_DISC;
+       radius = rad;
+}
+
+Disc::Disc(const Vec3 &normal, float dist, float rad)
+       : Plane(normal, dist)
+{
+       type = GOBJ_DISC;
+       radius = rad;
+}
+
+bool Disc::valid() const
+{
+       return radius >= 0.0f;
+}
+
+void Disc::invalidate()
+{
+       radius = -1;
+}
+
+bool Disc::intersect(const Ray &ray, HitPoint *hit) const
+{
+       HitPoint phit;
+       if(Plane::intersect(ray, &phit)) {
+               if(length_sq(phit.pos - pt) <= radius * radius) {
+                       *hit = phit;
+                       return true;
+               }
+       }
+       return false;
+}
+
+bool Disc::contains(const Vec3 &pt) const
+{
+       Vec3 pj = proj_point_plane(pt, *this);
+       return length_sq(pj - this->pt) <= radius * radius;
+}
+
+float Disc::distance_sq(const Vec3 &v) const
+{
+       return 0.0;     // TODO
+}
+
+float Disc::signed_distance_sq(const Vec3 &v) const
+{
+       return 0.0;     // TODO
+}
+
+
+Vec3 proj_point_plane(const Vec3 &pt, const Plane &plane)
+{
+       float dist = plane.signed_distance(pt);
+       return pt - plane.normal * dist;
+}
+
+// ---- bounding sphere calculations ----
+
+bool calc_bounding_sphere(Sphere *sph, const GeomObject *obj)
+{
+       if(!obj->valid()) {
+               sph->invalidate();
+               return true;
+       }
+
+       switch(obj->type) {
+       case GOBJ_SPHERE:
+               *sph = *(Sphere*)obj;
+               break;
+
+       case GOBJ_AABOX:
+               sph->center = (AABOX(obj)->min + AABOX(obj)->max) * 0.5;
+               sph->radius = length(AABOX(obj)->max - AABOX(obj)->min) * 0.5;
+               break;
+
+       case GOBJ_BOX:
+               sph->center = (BOX(obj)->min + BOX(obj)->max) * 0.5 + BOX(obj)->xform.get_translation();
+               sph->radius = length(BOX(obj)->max - BOX(obj)->min) * 0.5;
+               break;
+
+       case GOBJ_PLANE:
+       default:
+               return false;
+       }
+       return true;
+}
+
+bool calc_bounding_sphere(Sphere *sph, const GeomObject *a, const GeomObject *b)
+{
+       Sphere bsa, bsb;
+
+       if(!calc_bounding_sphere(&bsa, a) || !calc_bounding_sphere(&bsb, b)) {
+               return false;
+       }
+
+       float dist = length(bsa.center - bsb.center);
+       float surf_dist = dist - (bsa.radius + bsb.radius);
+       float d1 = bsa.radius + surf_dist / 2.0;
+       float d2 = bsb.radius + surf_dist / 2.0;
+       float t = d1 / (d1 + d2);
+
+       if(t < 0.0) t = 0.0;
+       if(t > 1.0) t = 1.0;
+
+       sph->center = bsa.center * t + bsb.center * (1.0 - t);
+       sph->radius = std::max(dist * t + bsb.radius, dist * (1.0f - t) + bsa.radius);
+       return true;
+}
+
+bool calc_bounding_sphere(Sphere *sph, const GeomObject **objv, int num)
+{
+       if(num <= 0) return false;
+
+       if(!calc_bounding_sphere(sph, objv[0])) {
+               return false;
+       }
+
+       for(int i=1; i<num; i++) {
+               if(!calc_bounding_sphere(sph, sph, objv[i])) {
+                       return false;
+               }
+       }
+       return true;
+}
+
+bool calc_bounding_sphere(Sphere *sph, const Vec3 *v, int num, const Mat4 &xform)
+{
+       if(num <= 0) return false;
+
+       sph->center = Vec3(0.0, 0.0, 0.0);
+       for(int i=0; i<num; i++) {
+               sph->center += xform * v[i];
+       }
+       sph->center /= (float)num;
+
+       float rad_sq = 0.0f;
+       for(int i=0; i<num; i++) {
+               Vec3 dir = xform * v[i] - sph->center;
+               rad_sq = std::max(rad_sq, dot(dir, dir));
+       }
+       sph->radius = sqrt(rad_sq);
+       return true;
+}
+
+bool calc_bounding_aabox(AABox *box, const GeomObject *obj)
+{
+       if(!obj->valid()) {
+               box->invalidate();
+               return true;
+       }
+
+       switch(obj->type) {
+       case GOBJ_AABOX:
+               *box = *(AABox*)obj;
+               break;
+
+       case GOBJ_BOX:
+               {
+                       Vec3 v[8];
+                       for(int i=0; i<8; i++) {
+                               v[i] = BOX(obj)->get_corner(i);
+                       }
+                       calc_bounding_aabox(box, v, 8);
+               }
+               break;
+
+       case GOBJ_SPHERE:
+               {
+                       float r = SPHERE(obj)->radius;
+                       box->min = SPHERE(obj)->center - Vec3(r, r, r);
+                       box->max = SPHERE(obj)->center + Vec3(r, r, r);
+               }
+               break;
+
+       case GOBJ_PLANE:
+       default:
+               return false;
+       }
+       return true;
+}
+
+bool calc_bounding_aabox(AABox *box, const GeomObject *a, const GeomObject *b)
+{
+       AABox bba, bbb;
+
+       if(!calc_bounding_aabox(&bba, a) || !calc_bounding_aabox(&bbb, b)) {
+               return false;
+       }
+
+       for(int i=0; i<3; i++) {
+               box->min[i] = std::min(bba.min[i], bbb.min[i]);
+               box->max[i] = std::max(bba.max[i], bbb.max[i]);
+       }
+       return true;
+}
+
+bool calc_bounding_aabox(AABox *box, const GeomObject **objv, int num)
+{
+       if(num <= 0) return false;
+
+       if(!calc_bounding_aabox(box, objv[0])) {
+               return false;
+       }
+
+       for(int i=1; i<num; i++) {
+               if(!calc_bounding_aabox(box, box, objv[i])) {
+                       return false;
+               }
+       }
+       return true;
+}
+
+bool calc_bounding_aabox(AABox *box, const Vec3 *v, int num, const Mat4 &xform)
+{
+       if(num <= 0) return false;
+
+       box->min = box->max = xform * v[0];
+       for(int i=1; i<num; i++) {
+               Vec3 p = xform * v[i];
+
+               for(int j=0; j<3; j++) {
+                       box->min[j] = std::min(box->min[j], p[j]);
+                       box->max[j] = std::max(box->max[j], p[j]);
+               }
+       }
+       return true;
+}
+
+bool calc_bounding_box(Box *box, const GeomObject *obj)
+{
+       if(!obj->valid()) {
+               box->invalidate();
+               return true;
+       }
+
+       switch(obj->type) {
+       case GOBJ_BOX:
+               *box = *(Box*)obj;
+               break;
+
+       case GOBJ_AABOX:
+               box->min = BOX(obj)->min;
+               box->max = BOX(obj)->max;
+               box->xform = Mat4::identity;
+               break;
+
+       case GOBJ_SPHERE:
+               {
+                       float r = SPHERE(obj)->radius;
+                       box->min = SPHERE(obj)->center - Vec3(r, r, r);
+                       box->max = SPHERE(obj)->center + Vec3(r, r, r);
+                       box->xform = Mat4::identity;
+               }
+               break;
+
+       case GOBJ_PLANE:
+       default:
+               return false;
+       }
+       return true;
+}
+
+bool intersect_sphere_sphere(Disc *result, const Sphere &a, const Sphere &b)
+{
+       Vec3 dir = b.center - a.center;
+
+       float dist_sq = length_sq(dir);
+       if(dist_sq <= 1e-8) return false;
+
+       float rsum = a.radius + b.radius;
+       float rdif = fabs(a.radius - b.radius);
+       if(dist_sq > rsum * rsum || dist_sq < rdif * rdif) {
+               return false;
+       }
+
+       float dist = sqrt(dist_sq);
+       float t = (dist_sq + a.radius * a.radius - b.radius * b.radius) / (2.0 * sqrt(dist_sq));
+
+       result->pt = a.center + dir * t;
+       result->normal = dir / dist;
+       result->radius = sin(acos(t)) * a.radius;
+       return true;
+}
+
+bool intersect_plane_plane(Ray *result, const Plane &a, const Plane &b)
+{
+       return false;   // TODO
+}
+
+bool intersect_sphere_plane(Sphere *result, const Sphere &s, const Plane &p)
+{
+       return false;   // TODO
+}
+
+bool intersect_plane_sphere(Sphere *result, const Plane &p, const Sphere &s)
+{
+       return false;   // TODO
+}
+
+bool intersect_aabox_aabox(AABox *res, const AABox &a, const AABox &b)
+{
+       for(int i=0; i<3; i++) {
+               res->min[i] = std::max(a.min[i], b.min[i]);
+               res->max[i] = std::min(a.max[i], b.max[i]);
+
+               if(res->max[i] < res->min[i]) {
+                       res->max[i] = res->min[i];
+               }
+       }
+       return res->min.x != res->max.x && res->min.y != res->max.y && res->min.z != res->max.z;
+}
+
+bool collision_sphere_aabox(const Sphere &s, const AABox &b)
+{
+       return b.distance_sq(s.center) <= s.radius * s.radius;
+}
diff --git a/src/geom.h b/src/geom.h
new file mode 100644 (file)
index 0000000..0d8dd00
--- /dev/null
@@ -0,0 +1,200 @@
+#ifndef GEOMOBJ_H_
+#define GEOMOBJ_H_
+
+/* TODO:
+ * - implement distance functions
+ */
+
+#include <gmath/gmath.h>
+
+enum GeomObjectType {
+       GOBJ_UNKNOWN,
+       GOBJ_SPHERE,
+       GOBJ_AABOX,
+       GOBJ_BOX,
+       GOBJ_PLANE,
+       GOBJ_DISC
+};
+
+enum {
+       AABOX_PLANE_PX,
+       AABOX_PLANE_NX,
+       AABOX_PLANE_PY,
+       AABOX_PLANE_NY,
+       AABOX_PLANE_PZ,
+       AABOX_PLANE_NZ
+};
+
+class GeomObject;
+class Plane;
+
+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:
+       GeomObjectType type;
+
+       GeomObject();
+       virtual ~GeomObject();
+
+       virtual bool valid() const;
+       virtual void invalidate();
+
+       virtual bool intersect(const Ray &ray, HitPoint *hit = 0) const = 0;
+       virtual bool contains(const Vec3 &pt) const = 0;
+
+       virtual float distance(const Vec3 &v) const;
+       virtual float signed_distance(const Vec3 &v) const;
+
+       virtual float distance_sq(const Vec3 &v) const = 0;
+       virtual float signed_distance_sq(const Vec3 &v) const = 0;
+};
+
+class Sphere : public GeomObject {
+public:
+       Vec3 center;
+       float radius;
+
+       Sphere();
+       Sphere(const Vec3 &center, float radius);
+
+       virtual bool valid() const;
+       virtual void invalidate();
+
+       virtual bool intersect(const Ray &ray, HitPoint *hit = 0) const;
+       virtual bool contains(const Vec3 &pt) const;
+
+       virtual float distance_sq(const Vec3 &v) const;
+       virtual float signed_distance_sq(const Vec3 &v) const;
+};
+
+class AABox : public GeomObject {
+public:
+       Vec3 min, max;
+
+       AABox();
+       AABox(const Vec3 &min, const Vec3 &max);
+
+       virtual bool valid() const;
+       virtual void invalidate();
+
+       virtual Vec3 get_corner(int idx) const;
+       virtual Plane get_plane(int pidx) const;
+
+       virtual bool intersect(const Ray &ray, HitPoint *hit = 0) const;
+       virtual bool contains(const Vec3 &pt) const;
+
+       virtual float distance_sq(const Vec3 &v) const;
+       virtual float signed_distance_sq(const Vec3 &v) const;
+};
+
+class Box : public AABox {
+public:
+       Mat4 xform;
+
+       Box();
+       Box(const AABox &aabox, const Mat4 &xform);
+       Box(const Vec3 &min, const Vec3 &max);
+       Box(const Vec3 &min, const Vec3 &max, const Mat4 &xform);
+       Box(const Vec3 &pos, const Vec3 &vi, const Vec3 &vj, const Vec3 &vk);
+       Box(const Vec3 *varr, int vcount);
+
+       virtual void invalidate();
+
+       virtual Vec3 get_corner(int idx) const;
+
+       virtual bool intersect(const Ray &ray, HitPoint *hit = 0) const;
+       virtual bool contains(const Vec3 &pt) const;
+
+       virtual float distance_sq(const Vec3 &v) const;
+       virtual float signed_distance_sq(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);
+
+       virtual bool intersect(const Ray &ray, HitPoint *hit = 0) const;
+       virtual bool contains(const Vec3 &pt) const;
+
+       virtual float distance(const Vec3 &v) const;
+       virtual float signed_distance(const Vec3 &v) const;
+
+       virtual float distance_sq(const Vec3 &v) const;
+       virtual float signed_distance_sq(const Vec3 &v) const;
+};
+
+class Disc : public Plane {
+public:
+       float radius;
+
+       Disc();
+       Disc(const Vec3 &pt, const Vec3 &normal, float rad);
+       Disc(const Vec3 &normal, float dist, float rad);
+
+       virtual bool valid() const;
+       virtual void invalidate();
+
+       virtual bool intersect(const Ray &ray, HitPoint *hit = 0) const;
+       //! true if the projection of pt to the plane is contained within the disc radius
+       virtual bool contains(const Vec3 &pt) const;
+
+       virtual float distance_sq(const Vec3 &v) const;
+       virtual float signed_distance_sq(const Vec3 &v) const;
+};
+
+//! project point to plane
+Vec3 proj_point_plane(const Vec3 &pt, const Plane &plane);
+
+//! calculate the bounding sphere of any object
+bool calc_bounding_sphere(Sphere *sph, const GeomObject *obj);
+//! calculate the bounding sphere of two objects
+bool calc_bounding_sphere(Sphere *sph, const GeomObject *a, const GeomObject *b);
+//! calculate the bounding sphere of multiple objects
+bool calc_bounding_sphere(Sphere *sph, const GeomObject **objv, int num);
+//! calculate the bounding sphere of multiple points, optionally transformed by `xform`
+bool calc_bounding_sphere(Sphere *sph, const Vec3 *v, int num, const Mat4 &xform = Mat4::identity);
+
+//! calculate the bounding axis-aligned box of any object
+bool calc_bounding_aabox(AABox *box, const GeomObject *obj);
+//! calculate the bounding axis-aligned box of two objects
+bool calc_bounding_aabox(AABox *box, const GeomObject *a, const GeomObject *b);
+//! calculate the bounding axis-aligned box of multiple objects
+bool calc_bounding_aabox(AABox *box, const GeomObject **objv, int num);
+//! calculate the bounding axis-aligned box of multiple points, optionally transformed by `xform`
+bool calc_bounding_aabox(AABox *box, const Vec3 *v, int num, const Mat4 &xform = Mat4::identity);
+
+//! calculate the bounding box of any object
+bool calc_bounding_box(Box *box, const GeomObject *obj);
+
+//! calculate the intersection plane of two spheres. false if there is no plane or infinite planes.
+bool intersect_sphere_sphere(Plane *result, const Sphere &a, const Sphere &b);
+//! calculate the intersection line of two planes. returns false if planes are exactly parallel.
+bool intersect_plane_plane(Ray *result, const Plane &a, const Plane &b);
+/*! calculate the intesection circle of a plane and a sphere. returns false if they don't intersect.
+ * \{
+ */
+bool intersect_sphere_plane(Sphere *result, const Sphere &s, const Plane &p);
+bool intersect_plane_sphere(Sphere *result, const Plane &p, const Sphere &s);
+//! \}
+
+//! calculate the intersection of two axis-aligned bounding boxes
+bool intersect_aabox_aabox(AABox *res, const AABox &a, const AABox &b);
+
+//! determine if a sphere and an axis-aligned box collide
+bool collision_sphere_aabox(const Sphere &s, const AABox &b);
+
+#endif // GEOMOBJ_H_
index ca23fbc..4c707d6 100644 (file)
@@ -30,6 +30,11 @@ int main(int argc, char **argv)
        glutInit(&argc, argv);
        glutInitWindowSize(1024, 600);
        glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_SRGB | GLUT_MULTISAMPLE);
        glutInit(&argc, argv);
        glutInitWindowSize(1024, 600);
        glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_SRGB | GLUT_MULTISAMPLE);
+
+       glutInitContextVersion(3, 0);
+       glutInitContextProfile(GLUT_COMPATIBILITY_PROFILE);
+       glutInitContextFlags(GLUT_DEBUG);
+
        glutCreateWindow("ludum dare 42");
 
        glutDisplayFunc(display);
        glutCreateWindow("ludum dare 42");
 
        glutDisplayFunc(display);
diff --git a/src/mesh.cc b/src/mesh.cc
new file mode 100644 (file)
index 0000000..798e8d1
--- /dev/null
@@ -0,0 +1,1443 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <float.h>
+#include <assert.h>
+#include "opengl.h"
+#include "mesh.h"
+//#include "logger.h"
+//#include "xform_node.h"
+
+#define USE_OLDGL
+
+bool Mesh::use_custom_sdr_attr = true;
+int Mesh::global_sdr_loc[NUM_MESH_ATTR] = { 0, 1, 2, 3, 4, 5, 6, 7 };
+unsigned int Mesh::intersect_mode = ISECT_DEFAULT;
+float Mesh::vertex_sel_dist = 0.01;
+float Mesh::vis_vecsize = 1.0;
+
+Mesh::Mesh()
+{
+       clear();
+
+       glGenBuffers(NUM_MESH_ATTR + 1, buffer_objects);
+
+       for(int i=0; i<NUM_MESH_ATTR; i++) {
+               vattr[i].vbo = buffer_objects[i];
+       }
+       ibo = buffer_objects[NUM_MESH_ATTR];
+       wire_ibo = 0;
+}
+
+Mesh::~Mesh()
+{
+       glDeleteBuffers(NUM_MESH_ATTR + 1, buffer_objects);
+
+       if(wire_ibo) {
+               glDeleteBuffers(1, &wire_ibo);
+       }
+}
+
+Mesh::Mesh(const Mesh &rhs)
+{
+       clear();
+
+       glGenBuffers(NUM_MESH_ATTR + 1, buffer_objects);
+
+       for(int i=0; i<NUM_MESH_ATTR; i++) {
+               vattr[i].vbo = buffer_objects[i];
+       }
+       ibo = buffer_objects[NUM_MESH_ATTR];
+       wire_ibo = 0;
+
+       clone(rhs);
+}
+
+Mesh &Mesh::operator =(const Mesh &rhs)
+{
+       if(&rhs != this) {
+               clone(rhs);
+       }
+       return *this;
+}
+
+bool Mesh::clone(const Mesh &m)
+{
+       clear();
+
+       for(int i=0; i<NUM_MESH_ATTR; i++) {
+               if(m.has_attrib(i)) {
+                       m.get_attrib_data(i);   // force validation of the actual data on the source mesh
+
+                       vattr[i].nelem = m.vattr[i].nelem;
+                       vattr[i].data = m.vattr[i].data;        // copy the actual data
+                       vattr[i].data_valid = true;
+               }
+       }
+
+       if(m.is_indexed()) {
+               m.get_index_data();             // again, force validation
+
+               // copy the index data
+               idata = m.idata;
+               idata_valid = true;
+       }
+
+       name = m.name;
+       nverts = m.nverts;
+       nfaces = m.nfaces;
+
+       //bones = m.bones;
+
+       memcpy(cur_val, m.cur_val, sizeof cur_val);
+
+       aabb = m.aabb;
+       aabb_valid = m.aabb_valid;
+       bsph = m.bsph;
+       bsph_valid = m.bsph_valid;
+
+       hitface = m.hitface;
+       hitvert = m.hitvert;
+
+       intersect_mode = m.intersect_mode;
+       vertex_sel_dist = m.vertex_sel_dist;
+       vis_vecsize = m.vis_vecsize;
+
+       return true;
+}
+
+void Mesh::set_name(const char *name)
+{
+       this->name = name;
+}
+
+const char *Mesh::get_name() const
+{
+       return name.c_str();
+}
+
+bool Mesh::has_attrib(int attr) const
+{
+       if(attr < 0 || attr >= NUM_MESH_ATTR) {
+               return false;
+       }
+
+       // if neither of these is valid, then nobody has set this attribute
+       return vattr[attr].vbo_valid || vattr[attr].data_valid;
+}
+
+bool Mesh::is_indexed() const
+{
+       return ibo_valid || idata_valid;
+}
+
+void Mesh::clear()
+{
+       //bones.clear();
+
+       for(int i=0; i<NUM_MESH_ATTR; i++) {
+               vattr[i].nelem = 0;
+               vattr[i].vbo_valid = false;
+               vattr[i].data_valid = false;
+               //vattr[i].sdr_loc = -1;
+               vattr[i].data.clear();
+       }
+       ibo_valid = idata_valid = false;
+       idata.clear();
+
+       wire_ibo_valid = false;
+
+       nverts = nfaces = 0;
+
+       bsph_valid = false;
+       aabb_valid = false;
+}
+
+float *Mesh::set_attrib_data(int attrib, int nelem, unsigned int num, const float *data)
+{
+       if(attrib < 0 || attrib >= NUM_MESH_ATTR) {
+               fprintf(stderr, "%s: invalid attrib: %d\n", __FUNCTION__, attrib);
+               return 0;
+       }
+
+       if(nverts && num != nverts) {
+               fprintf(stderr, "%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) {
+               fprintf(stderr, "%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) {
+               fprintf(stderr, "%s: invalid attrib: %d\n", __FUNCTION__, attrib);
+               return 0;
+       }
+
+       if(!vattr[attrib].data_valid) {
+#if GL_ES_VERSION_2_0
+               fprintf(stderr, "%s: can't read back attrib data on CrippledGL ES\n", __FUNCTION__);
+               return 0;
+#else
+               if(!vattr[attrib].vbo_valid) {
+                       fprintf(stderr, "%s: unavailable attrib: %d\n", __FUNCTION__, attrib);
+                       return 0;
+               }
+
+               // local data copy is unavailable, grab the data from the vbo
+               Mesh *m = (Mesh*)this;
+               m->vattr[attrib].data.resize(nverts * vattr[attrib].nelem);
+
+               glBindBuffer(GL_ARRAY_BUFFER, vattr[attrib].vbo);
+               void *data = glMapBuffer(GL_ARRAY_BUFFER, GL_READ_ONLY);
+               memcpy(&m->vattr[attrib].data[0], data, nverts * vattr[attrib].nelem * sizeof(float));
+               glUnmapBuffer(GL_ARRAY_BUFFER);
+
+               vattr[attrib].data_valid = true;
+#endif
+       }
+
+       return &vattr[attrib].data[0];
+}
+
+void Mesh::set_attrib(int attrib, int idx, const Vec4 &v)
+{
+       float *data = get_attrib_data(attrib);
+       if(data) {
+               data += idx * vattr[attrib].nelem;
+               for(int i=0; i<vattr[attrib].nelem; i++) {
+                       data[i] = v[i];
+               }
+       }
+}
+
+Vec4 Mesh::get_attrib(int attrib, int idx) const
+{
+       Vec4 v(0.0, 0.0, 0.0, 1.0);
+       const float *data = get_attrib_data(attrib);
+       if(data) {
+               data += idx * vattr[attrib].nelem;
+               for(int i=0; i<vattr[attrib].nelem; i++) {
+                       v[i] = data[i];
+               }
+       }
+       return v;
+}
+
+int Mesh::get_attrib_count(int attrib) const
+{
+       return has_attrib(attrib) ? nverts : 0;
+}
+
+
+unsigned int *Mesh::set_index_data(int num, const unsigned int *indices)
+{
+       int nidx = nfaces * 3;
+       if(nidx && num != nidx) {
+               fprintf(stderr, "%s: index count mismatch (%d instead of %d)\n", __FUNCTION__, num, nidx);
+               return 0;
+       }
+       nfaces = num / 3;
+
+       idata.clear();
+       idata.resize(num);
+
+       if(indices) {
+               memcpy(&idata[0], indices, num * sizeof *indices);
+       }
+
+       idata_valid = true;
+       ibo_valid = false;
+
+       return &idata[0];
+}
+
+unsigned int *Mesh::get_index_data()
+{
+       ibo_valid = false;
+       return (unsigned int*)((const Mesh*)this)->get_index_data();
+}
+
+const unsigned int *Mesh::get_index_data() const
+{
+       if(!idata_valid) {
+#if GL_ES_VERSION_2_0
+               fprintf(stderr, "%s: can't read back index data in CrippledGL ES\n", __FUNCTION__);
+               return 0;
+#else
+               if(!ibo_valid) {
+                       fprintf(stderr, "%s: indices unavailable\n", __FUNCTION__);
+                       return 0;
+               }
+
+               // local data copy is unavailable, gram the data from the ibo
+               Mesh *m = (Mesh*)this;
+               int nidx = nfaces * 3;
+               m->idata.resize(nidx);
+
+               glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
+               void *data = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_READ_ONLY);
+               memcpy(&m->idata[0], data, nidx * sizeof(unsigned int));
+               glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
+
+               idata_valid = true;
+#endif
+       }
+
+       return &idata[0];
+}
+
+int Mesh::get_index_count() const
+{
+       return nfaces * 3;
+}
+
+void Mesh::append(const Mesh &mesh)
+{
+       unsigned int idxoffs = nverts;
+
+       if(!nverts) {
+               clone(mesh);
+               return;
+       }
+
+       nverts += mesh.nverts;
+       nfaces += mesh.nfaces;
+
+       for(int i=0; i<NUM_MESH_ATTR; i++) {
+               if(has_attrib(i) && mesh.has_attrib(i)) {
+                       // force validating the data arrays
+                       get_attrib_data(i);
+                       mesh.get_attrib_data(i);
+
+                       // append the mesh data
+                       vattr[i].data.insert(vattr[i].data.end(), mesh.vattr[i].data.begin(), mesh.vattr[i].data.end());
+               }
+       }
+
+       if(ibo_valid || idata_valid) {
+               // make index arrays valid
+               get_index_data();
+               mesh.get_index_data();
+
+               size_t orig_sz = idata.size();
+
+               idata.insert(idata.end(), mesh.idata.begin(), mesh.idata.end());
+
+               // fixup all the new indices
+               for(size_t i=orig_sz; i<idata.size(); i++) {
+                       idata[i] += idxoffs;
+               }
+       }
+
+       // fuck everything
+       wire_ibo_valid = false;
+       aabb_valid = false;
+       bsph_valid = false;
+}
+
+// assemble a complete vertex by adding all the useful attributes
+void Mesh::vertex(float x, float y, float z)
+{
+       cur_val[MESH_ATTR_VERTEX] = Vec4(x, y, z, 1.0f);
+       vattr[MESH_ATTR_VERTEX].data_valid = true;
+       vattr[MESH_ATTR_VERTEX].nelem = 3;
+
+       for(int i=0; i<NUM_MESH_ATTR; i++) {
+               if(vattr[i].data_valid) {
+                       for(int j=0; j<vattr[MESH_ATTR_VERTEX].nelem; j++) {
+                               vattr[i].data.push_back(cur_val[i][j]);
+                       }
+               }
+               vattr[i].vbo_valid = false;
+       }
+
+       if(idata_valid) {
+               idata.clear();
+       }
+       ibo_valid = idata_valid = false;
+}
+
+void Mesh::normal(float nx, float ny, float nz)
+{
+       cur_val[MESH_ATTR_NORMAL] = Vec4(nx, ny, nz, 1.0f);
+       vattr[MESH_ATTR_NORMAL].data_valid = true;
+       vattr[MESH_ATTR_NORMAL].nelem = 3;
+}
+
+void Mesh::tangent(float tx, float ty, float tz)
+{
+       cur_val[MESH_ATTR_TANGENT] = Vec4(tx, ty, tz, 1.0f);
+       vattr[MESH_ATTR_TANGENT].data_valid = true;
+       vattr[MESH_ATTR_TANGENT].nelem = 3;
+}
+
+void Mesh::texcoord(float u, float v, float w)
+{
+       cur_val[MESH_ATTR_TEXCOORD] = Vec4(u, v, w, 1.0f);
+       vattr[MESH_ATTR_TEXCOORD].data_valid = true;
+       vattr[MESH_ATTR_TEXCOORD].nelem = 3;
+}
+
+void Mesh::boneweights(float w1, float w2, float w3, float w4)
+{
+       cur_val[MESH_ATTR_BONEWEIGHTS] = Vec4(w1, w2, w3, w4);
+       vattr[MESH_ATTR_BONEWEIGHTS].data_valid = true;
+       vattr[MESH_ATTR_BONEWEIGHTS].nelem = 4;
+}
+
+void Mesh::boneidx(int idx1, int idx2, int idx3, int idx4)
+{
+       cur_val[MESH_ATTR_BONEIDX] = Vec4(idx1, idx2, idx3, idx4);
+       vattr[MESH_ATTR_BONEIDX].data_valid = true;
+       vattr[MESH_ATTR_BONEIDX].nelem = 4;
+}
+
+int Mesh::get_poly_count() const
+{
+       if(nfaces) {
+               return nfaces;
+       }
+       if(nverts) {
+               return nverts / 3;
+       }
+       return 0;
+}
+
+/// static function
+void Mesh::set_attrib_location(int attr, int loc)
+{
+       if(attr < 0 || attr >= NUM_MESH_ATTR) {
+               return;
+       }
+       Mesh::global_sdr_loc[attr] = loc;
+}
+
+/// static function
+int Mesh::get_attrib_location(int attr)
+{
+       if(attr < 0 || attr >= NUM_MESH_ATTR) {
+               return -1;
+       }
+       return Mesh::global_sdr_loc[attr];
+}
+
+/// static function
+void Mesh::clear_attrib_locations()
+{
+       for(int i=0; i<NUM_MESH_ATTR; i++) {
+               Mesh::global_sdr_loc[i] = -1;
+       }
+}
+
+/// static function
+void Mesh::set_vis_vecsize(float sz)
+{
+       Mesh::vis_vecsize = sz;
+}
+
+float Mesh::get_vis_vecsize()
+{
+       return Mesh::vis_vecsize;
+}
+
+void Mesh::apply_xform(const Mat4 &xform)
+{
+       Mat4 dir_xform = xform.upper3x3();
+       apply_xform(xform, dir_xform);
+}
+
+void Mesh::apply_xform(const Mat4 &xform, const Mat4 &dir_xform)
+{
+       for(unsigned int i=0; i<nverts; i++) {
+               Vec4 v = get_attrib(MESH_ATTR_VERTEX, i);
+               set_attrib(MESH_ATTR_VERTEX, i, xform * v);
+
+               if(has_attrib(MESH_ATTR_NORMAL)) {
+                       Vec3 n = Vec3(get_attrib(MESH_ATTR_NORMAL, i));
+                       set_attrib(MESH_ATTR_NORMAL, i, Vec4(dir_xform * n));
+               }
+               if(has_attrib(MESH_ATTR_TANGENT)) {
+                       Vec3 t = Vec3(get_attrib(MESH_ATTR_TANGENT, i));
+                       set_attrib(MESH_ATTR_TANGENT, i, Vec4(dir_xform * t));
+               }
+       }
+}
+
+void Mesh::flip()
+{
+       flip_faces();
+       flip_normals();
+}
+
+void Mesh::flip_faces()
+{
+       if(is_indexed()) {
+               unsigned int *indices = get_index_data();
+               if(!indices) return;
+
+               int idxnum = get_index_count();
+               for(int i=0; i<idxnum; i+=3) {
+                       unsigned int tmp = indices[i + 2];
+                       indices[i + 2] = indices[i + 1];
+                       indices[i + 1] = tmp;
+               }
+
+       } else {
+               Vec3 *verts = (Vec3*)get_attrib_data(MESH_ATTR_VERTEX);
+               if(!verts) return;
+
+               int vnum = get_attrib_count(MESH_ATTR_VERTEX);
+               for(int i=0; i<vnum; i+=3) {
+                       Vec3 tmp = verts[i + 2];
+                       verts[i + 2] = verts[i + 1];
+                       verts[i + 1] = tmp;
+               }
+       }
+}
+
+void Mesh::flip_normals()
+{
+       Vec3 *normals = (Vec3*)get_attrib_data(MESH_ATTR_NORMAL);
+       if(!normals) return;
+
+       int num = get_attrib_count(MESH_ATTR_NORMAL);
+       for(int i=0; i<num; i++) {
+               normals[i] = -normals[i];
+       }
+}
+
+void Mesh::explode()
+{
+       if(!is_indexed()) return;       // no vertex sharing is possible in unindexed meshes
+
+       unsigned int *indices = get_index_data();
+       assert(indices);
+
+       int idxnum = get_index_count();
+       int nnverts = idxnum;
+
+       nverts = nnverts;
+
+       for(int i=0; i<NUM_MESH_ATTR; i++) {
+               if(!has_attrib(i)) continue;
+
+               const float *srcbuf = get_attrib_data(i);
+
+               float *tmpbuf = new float[nnverts * vattr[i].nelem];
+               float *dstptr = tmpbuf;
+               for(int j=0; j<idxnum; j++) {
+                       unsigned int idx = indices[j];
+                       const float *srcptr = srcbuf + idx * vattr[i].nelem;
+
+                       for(int k=0; k<vattr[i].nelem; k++) {
+                               *dstptr++ = *srcptr++;
+                       }
+               }
+               set_attrib_data(i, vattr[i].nelem, nnverts, tmpbuf);
+               delete [] tmpbuf;
+       }
+
+       ibo_valid = false;
+       idata_valid = false;
+       idata.clear();
+
+       nfaces = idxnum / 3;
+}
+
+void Mesh::calc_face_normals()
+{
+       const Vec3 *varr = (Vec3*)get_attrib_data(MESH_ATTR_VERTEX);
+       Vec3 *narr = (Vec3*)get_attrib_data(MESH_ATTR_NORMAL);
+       if(!varr) {
+               return;
+       }
+
+       if(is_indexed()) {
+               const unsigned int *idxarr = get_index_data();
+
+               for(unsigned int i=0; i<nfaces; i++) {
+                       Triangle face(i, varr, idxarr);
+                       face.calc_normal();
+
+                       for(int j=0; j<3; j++) {
+                               unsigned int idx = *idxarr++;
+                               narr[idx] = face.normal;
+                       }
+               }
+       } else {
+               // non-indexed
+               int nfaces = nverts / 3;
+
+               for(int i=0; i<nfaces; i++) {
+                       Triangle face(varr[0], varr[1], varr[2]);
+                       face.calc_normal();
+
+                       narr[0] = narr[1] = narr[2] = face.normal;
+                       varr += vattr[MESH_ATTR_VERTEX].nelem;
+                       narr += vattr[MESH_ATTR_NORMAL].nelem;
+               }
+       }
+}
+
+/*
+int Mesh::add_bone(XFormNode *bone)
+{
+       int idx = bones.size();
+       bones.push_back(bone);
+       return idx;
+}
+
+const XFormNode *Mesh::get_bone(int idx) const
+{
+       if(idx < 0 || idx >= (int)bones.size()) {
+               return 0;
+       }
+       return bones[idx];
+}
+
+int Mesh::get_bones_count() const
+{
+       return (int)bones.size();
+}
+*/
+
+bool Mesh::pre_draw() const
+{
+       cur_sdr = 0;
+       glGetIntegerv(GL_CURRENT_PROGRAM, &cur_sdr);
+
+       ((Mesh*)this)->update_buffers();
+
+       if(!vattr[MESH_ATTR_VERTEX].vbo_valid) {
+               fprintf(stderr, "%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) {
+                       fprintf(stderr, "%s: shader attribute location for vertices unset\n", __FUNCTION__);
+                       return false;
+               }
+
+               for(int i=0; i<NUM_MESH_ATTR; i++) {
+                       int loc = global_sdr_loc[i];
+                       if(loc >= 0 && vattr[i].vbo_valid) {
+                               glBindBuffer(GL_ARRAY_BUFFER, vattr[i].vbo);
+                               glVertexAttribPointer(loc, vattr[i].nelem, GL_FLOAT, GL_FALSE, 0, 0);
+                               glEnableVertexAttribArray(loc);
+                       }
+               }
+       } else {
+#ifndef GL_ES_VERSION_2_0
+               // rendering with fixed-function (not available in GLES2)
+               glBindBuffer(GL_ARRAY_BUFFER, vattr[MESH_ATTR_VERTEX].vbo);
+               glVertexPointer(vattr[MESH_ATTR_VERTEX].nelem, GL_FLOAT, 0, 0);
+               glEnableClientState(GL_VERTEX_ARRAY);
+
+               if(vattr[MESH_ATTR_NORMAL].vbo_valid) {
+                       glBindBuffer(GL_ARRAY_BUFFER, vattr[MESH_ATTR_NORMAL].vbo);
+                       glNormalPointer(GL_FLOAT, 0, 0);
+                       glEnableClientState(GL_NORMAL_ARRAY);
+               }
+               if(vattr[MESH_ATTR_TEXCOORD].vbo_valid) {
+                       glBindBuffer(GL_ARRAY_BUFFER, vattr[MESH_ATTR_TEXCOORD].vbo);
+                       glTexCoordPointer(vattr[MESH_ATTR_TEXCOORD].nelem, GL_FLOAT, 0, 0);
+                       glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+               }
+               if(vattr[MESH_ATTR_COLOR].vbo_valid) {
+                       glBindBuffer(GL_ARRAY_BUFFER, vattr[MESH_ATTR_COLOR].vbo);
+                       glColorPointer(vattr[MESH_ATTR_COLOR].nelem, GL_FLOAT, 0, 0);
+                       glEnableClientState(GL_COLOR_ARRAY);
+               }
+               if(vattr[MESH_ATTR_TEXCOORD2].vbo_valid) {
+                       glClientActiveTexture(GL_TEXTURE1);
+                       glBindBuffer(GL_ARRAY_BUFFER, vattr[MESH_ATTR_TEXCOORD2].vbo);
+                       glTexCoordPointer(vattr[MESH_ATTR_TEXCOORD2].nelem, GL_FLOAT, 0, 0);
+                       glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+                       glClientActiveTexture(GL_TEXTURE0);
+               }
+#endif
+       }
+       glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+       return true;
+}
+
+void Mesh::draw() const
+{
+       if(!pre_draw()) return;
+
+       if(ibo_valid) {
+               glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
+               glDrawElements(GL_TRIANGLES, nfaces * 3, GL_UNSIGNED_INT, 0);
+               glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+       } else {
+               glDrawArrays(GL_TRIANGLES, 0, nverts);
+       }
+
+       post_draw();
+}
+
+void Mesh::post_draw() const
+{
+       if(cur_sdr && use_custom_sdr_attr) {
+               // rendered with shaders
+               for(int i=0; i<NUM_MESH_ATTR; i++) {
+                       int loc = global_sdr_loc[i];
+                       if(loc >= 0 && vattr[i].vbo_valid) {
+                               glDisableVertexAttribArray(loc);
+                       }
+               }
+       } else {
+#ifndef GL_ES_VERSION_2_0
+               // rendered with fixed-function
+               glDisableClientState(GL_VERTEX_ARRAY);
+               if(vattr[MESH_ATTR_NORMAL].vbo_valid) {
+                       glDisableClientState(GL_NORMAL_ARRAY);
+               }
+               if(vattr[MESH_ATTR_TEXCOORD].vbo_valid) {
+                       glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+               }
+               if(vattr[MESH_ATTR_COLOR].vbo_valid) {
+                       glDisableClientState(GL_COLOR_ARRAY);
+               }
+               if(vattr[MESH_ATTR_TEXCOORD2].vbo_valid) {
+                       glClientActiveTexture(GL_TEXTURE1);
+                       glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+                       glClientActiveTexture(GL_TEXTURE0);
+               }
+#endif
+       }
+}
+
+void Mesh::draw_wire() const
+{
+       if(!pre_draw()) return;
+
+       ((Mesh*)this)->update_wire_ibo();
+
+       int num_faces = get_poly_count();
+       glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, wire_ibo);
+       glDrawElements(GL_LINES, num_faces * 6, GL_UNSIGNED_INT, 0);
+       glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+       post_draw();
+}
+
+void Mesh::draw_vertices() const
+{
+       if(!pre_draw()) return;
+
+       glDrawArrays(GL_POINTS, 0, nverts);
+
+       post_draw();
+}
+
+void Mesh::draw_normals() const
+{
+#ifdef USE_OLDGL
+       int cur_sdr = 0;
+       glGetIntegerv(GL_CURRENT_PROGRAM, &cur_sdr);
+
+       Vec3 *varr = (Vec3*)get_attrib_data(MESH_ATTR_VERTEX);
+       Vec3 *norm = (Vec3*)get_attrib_data(MESH_ATTR_NORMAL);
+       if(!varr || !norm) {
+               return;
+       }
+
+       glBegin(GL_LINES);
+       if(cur_sdr && use_custom_sdr_attr) {
+               int vert_loc = global_sdr_loc[MESH_ATTR_VERTEX];
+               if(vert_loc < 0) {
+                       glEnd();
+                       return;
+               }
+
+               for(size_t i=0; i<nverts; i++) {
+                       glVertexAttrib3f(vert_loc, varr[i].x, varr[i].y, varr[i].z);
+                       Vec3 end = varr[i] + norm[i] * vis_vecsize;
+                       glVertexAttrib3f(vert_loc, end.x, end.y, end.z);
+               }
+       } else {
+               for(size_t i=0; i<nverts; i++) {
+                       glVertex3f(varr[i].x, varr[i].y, varr[i].z);
+                       Vec3 end = varr[i] + norm[i] * vis_vecsize;
+                       glVertex3f(end.x, end.y, end.z);
+               }
+       }
+       glEnd();
+#endif // USE_OLDGL
+}
+
+void Mesh::draw_tangents() const
+{
+#ifdef USE_OLDGL
+       int cur_sdr = 0;
+       glGetIntegerv(GL_CURRENT_PROGRAM, &cur_sdr);
+
+       Vec3 *varr = (Vec3*)get_attrib_data(MESH_ATTR_VERTEX);
+       Vec3 *tang = (Vec3*)get_attrib_data(MESH_ATTR_TANGENT);
+       if(!varr || !tang) {
+               return;
+       }
+
+       glBegin(GL_LINES);
+       if(cur_sdr && use_custom_sdr_attr) {
+               int vert_loc = global_sdr_loc[MESH_ATTR_VERTEX];
+               if(vert_loc < 0) {
+                       glEnd();
+                       return;
+               }
+
+               for(size_t i=0; i<nverts; i++) {
+                       glVertexAttrib3f(vert_loc, varr[i].x, varr[i].y, varr[i].z);
+                       Vec3 end = varr[i] + tang[i] * vis_vecsize;
+                       glVertexAttrib3f(vert_loc, end.x, end.y, end.z);
+               }
+       } else {
+               for(size_t i=0; i<nverts; i++) {
+                       glVertex3f(varr[i].x, varr[i].y, varr[i].z);
+                       Vec3 end = varr[i] + tang[i] * vis_vecsize;
+                       glVertex3f(end.x, end.y, end.z);
+               }
+       }
+       glEnd();
+#endif // USE_OLDGL
+}
+
+void Mesh::get_aabbox(Vec3 *vmin, Vec3 *vmax) const
+{
+       if(!aabb_valid) {
+               ((Mesh*)this)->calc_aabb();
+       }
+       *vmin = aabb.min;
+       *vmax = aabb.max;
+}
+
+const AABox &Mesh::get_aabbox() const
+{
+       if(!aabb_valid) {
+               ((Mesh*)this)->calc_aabb();
+       }
+       return aabb;
+}
+
+float Mesh::get_bsphere(Vec3 *center, float *rad) const
+{
+       if(!bsph_valid) {
+               ((Mesh*)this)->calc_bsph();
+       }
+       *center = bsph.center;
+       *rad = bsph.radius;
+       return bsph.radius;
+}
+
+const Sphere &Mesh::get_bsphere() const
+{
+       if(!bsph_valid) {
+               ((Mesh*)this)->calc_bsph();
+       }
+       return bsph;
+}
+
+/// static function
+void Mesh::set_intersect_mode(unsigned int mode)
+{
+       Mesh::intersect_mode = mode;
+}
+
+/// static function
+unsigned int Mesh::get_intersect_mode()
+{
+       return Mesh::intersect_mode;
+}
+
+/// static function
+void Mesh::set_vertex_select_distance(float dist)
+{
+       Mesh::vertex_sel_dist = dist;
+}
+
+/// static function
+float Mesh::get_vertex_select_distance()
+{
+       return Mesh::vertex_sel_dist;
+}
+
+bool Mesh::intersect(const Ray &ray, HitPoint *hit) const
+{
+       assert((Mesh::intersect_mode & (ISECT_VERTICES | ISECT_FACE)) != (ISECT_VERTICES | ISECT_FACE));
+
+       const Vec3 *varr = (Vec3*)get_attrib_data(MESH_ATTR_VERTEX);
+       const Vec3 *narr = (Vec3*)get_attrib_data(MESH_ATTR_NORMAL);
+       if(!varr) {
+               return false;
+       }
+       const unsigned int *idxarr = get_index_data();
+
+       // first test with the bounding box
+       AABox box;
+       get_aabbox(&box.min, &box.max);
+       if(!box.intersect(ray)) {
+               return false;
+       }
+
+       HitPoint nearest_hit;
+       nearest_hit.dist = FLT_MAX;
+       nearest_hit.data = 0;
+
+       if(Mesh::intersect_mode & ISECT_VERTICES) {
+               // we asked for "intersections" with the vertices of the mesh
+               long nearest_vidx = -1;
+               float thres_sq = Mesh::vertex_sel_dist * Mesh::vertex_sel_dist;
+
+               for(unsigned int i=0; i<nverts; i++) {
+
+                       if((Mesh::intersect_mode & ISECT_FRONT) && dot(narr[i], ray.dir) > 0) {
+                               continue;
+                       }
+
+                       // project the vertex onto the ray line
+                       float t = dot(varr[i] - ray.origin, ray.dir);
+                       Vec3 vproj = ray.origin + ray.dir * t;
+
+                       float dist_sq = length_sq(vproj - varr[i]);
+                       if(dist_sq < thres_sq) {
+                               if(!hit) {
+                                       return true;
+                               }
+                               if(t < nearest_hit.dist) {
+                                       nearest_hit.dist = t;
+                                       nearest_vidx = i;
+                               }
+                       }
+               }
+
+               if(nearest_vidx != -1) {
+                       hitvert = varr[nearest_vidx];
+                       nearest_hit.data = &hitvert;
+               }
+
+       } else {
+               // regular intersection test with polygons
+
+               for(unsigned int i=0; i<nfaces; i++) {
+                       Triangle face(i, varr, idxarr);
+
+                       // ignore back-facing polygons if the mode flags include ISECT_FRONT
+                       if((Mesh::intersect_mode & ISECT_FRONT) && dot(face.get_normal(), ray.dir) > 0) {
+                               continue;
+                       }
+
+                       HitPoint fhit;
+                       if(face.intersect(ray, hit ? &fhit : 0)) {
+                               if(!hit) {
+                                       return true;
+                               }
+                               if(fhit.dist < nearest_hit.dist) {
+                                       nearest_hit = fhit;
+                                       hitface = face;
+                               }
+                       }
+               }
+       }
+
+       if(nearest_hit.data) {
+               if(hit) {
+                       *hit = nearest_hit;
+
+                       // if we are interested in the mesh and not the faces set obj to this
+                       if(Mesh::intersect_mode & ISECT_FACE) {
+                               hit->data = &hitface;
+                       } else if(Mesh::intersect_mode & ISECT_VERTICES) {
+                               hit->data = &hitvert;
+                       } else {
+                               hit->data = (void*)this;
+                       }
+               }
+               return true;
+       }
+       return false;
+}
+
+
+// texture coordinate manipulation
+void Mesh::texcoord_apply_xform(const Mat4 &xform)
+{
+       if(!has_attrib(MESH_ATTR_TEXCOORD)) {
+               return;
+       }
+
+       for(unsigned int i=0; i<nverts; i++) {
+               Vec4 tc = get_attrib(MESH_ATTR_TEXCOORD, i);
+               set_attrib(MESH_ATTR_TEXCOORD, i, xform * tc);
+       }
+}
+
+void Mesh::texcoord_gen_plane(const Vec3 &norm, const Vec3 &tang)
+{
+       if(!nverts) return;
+
+       if(!has_attrib(MESH_ATTR_TEXCOORD)) {
+               // allocate texture coordinate attribute array
+               set_attrib_data(MESH_ATTR_TEXCOORD, 2, nverts);
+       }
+
+       Vec3 n = normalize(norm);
+       Vec3 b = normalize(cross(n, tang));
+       Vec3 t = cross(b, n);
+
+       for(unsigned int i=0; i<nverts; i++) {
+               Vec3 pos = Vec3(get_attrib(MESH_ATTR_VERTEX, i));
+
+               // distance along the tangent direction
+               float u = dot(pos, t);
+               // distance along the bitangent direction
+               float v = dot(pos, b);
+
+               set_attrib(MESH_ATTR_TEXCOORD, i, Vec4(u, v, 0, 1));
+       }
+}
+
+void Mesh::texcoord_gen_box()
+{
+       if(!nverts || !has_attrib(MESH_ATTR_NORMAL)) return;
+
+       if(!has_attrib(MESH_ATTR_TEXCOORD)) {
+               // allocate texture coordinate attribute array
+               set_attrib_data(MESH_ATTR_TEXCOORD, 2, nverts);
+       }
+
+       for(unsigned int i=0; i<nverts; i++) {
+               Vec3 pos = Vec3(get_attrib(MESH_ATTR_VERTEX, i)) * 0.5 + Vec3(0.5, 0.5, 0.5);
+               Vec3 norm = Vec3(get_attrib(MESH_ATTR_NORMAL, i));
+
+               float abs_nx = fabs(norm.x);
+               float abs_ny = fabs(norm.y);
+               float abs_nz = fabs(norm.z);
+               int dom = abs_nx > abs_ny && abs_nx > abs_nz ? 0 : (abs_ny > abs_nz ? 1 : 2);
+
+               float uv[2], *uvptr = uv;
+               for(int j=0; j<3; j++) {
+                       if(j == dom) continue;  // skip dominant axis
+
+                       *uvptr++ = pos[j];
+               }
+               set_attrib(MESH_ATTR_TEXCOORD, i, Vec4(uv[0], uv[1], 0, 1));
+       }
+}
+
+void Mesh::texcoord_gen_cylinder()
+{
+       if(!nverts) return;
+
+       if(!has_attrib(MESH_ATTR_TEXCOORD)) {
+               // allocate texture coordinate attribute array
+               set_attrib_data(MESH_ATTR_TEXCOORD, 2, nverts);
+       }
+
+       for(unsigned int i=0; i<nverts; i++) {
+               Vec3 pos = Vec3(get_attrib(MESH_ATTR_VERTEX, i));
+
+               float rho = sqrt(pos.x * pos.x + pos.z * pos.z);
+               float theta = rho == 0.0 ? 0.0 : atan2(pos.z / rho, pos.x / rho);
+
+               float u = theta / (2.0 * M_PI) + 0.5;
+               float v = pos.y;
+
+               set_attrib(MESH_ATTR_TEXCOORD, i, Vec4(u, v, 0, 1));
+       }
+}
+
+
+bool Mesh::dump(const char *fname) const
+{
+       FILE *fp = fopen(fname, "wb");
+       if(fp) {
+               bool res = dump(fp);
+               fclose(fp);
+               return res;
+       }
+       return false;
+}
+
+bool Mesh::dump(FILE *fp) const
+{
+       if(!has_attrib(MESH_ATTR_VERTEX)) {
+               return false;
+       }
+
+       fprintf(fp, "VERTEX ATTRIBUTES\n");
+       static const char *label[] = { "pos", "nor", "tan", "tex", "col", "bw", "bid", "tex2" };
+       static const char *elemfmt[] = { 0, " %s(%g)", " %s(%g, %g)", " %s(%g, %g, %g)", " %s(%g, %g, %g, %g)", 0 };
+
+       for(int i=0; i<(int)nverts; i++) {
+               fprintf(fp, "%5u:", i);
+               for(int j=0; j<NUM_MESH_ATTR; j++) {
+                       if(has_attrib(j)) {
+                               Vec4 v = get_attrib(j, i);
+                               int nelem = vattr[j].nelem;
+                               fprintf(fp, elemfmt[nelem], label[j], v.x, v.y, v.z, v.w);
+                       }
+               }
+               fputc('\n', fp);
+       }
+
+       if(is_indexed()) {
+               const unsigned int *idx = get_index_data();
+               int numidx = get_index_count();
+               int numtri = numidx / 3;
+               assert(numidx % 3 == 0);
+
+               fprintf(fp, "FACES\n");
+
+               for(int i=0; i<numtri; i++) {
+                       fprintf(fp, "%5d: %d %d %d\n", i, idx[0], idx[1], idx[2]);
+                       idx += 3;
+               }
+       }
+       return true;
+}
+
+bool Mesh::dump_obj(const char *fname) const
+{
+       FILE *fp = fopen(fname, "wb");
+       if(fp) {
+               bool res = dump_obj(fp);
+               fclose(fp);
+               return res;
+       }
+       return false;
+}
+
+bool Mesh::dump_obj(FILE *fp, int voffs) const
+{
+       if(!has_attrib(MESH_ATTR_VERTEX)) {
+               return false;
+       }
+
+       for(int i=0; i<(int)nverts; i++) {
+               Vec4 v = get_attrib(MESH_ATTR_VERTEX, i);
+               fprintf(fp, "v %f %f %f\n", v.x, v.y, v.z);
+       }
+
+       if(has_attrib(MESH_ATTR_NORMAL)) {
+               for(int i=0; i<(int)nverts; i++) {
+                       Vec4 v = get_attrib(MESH_ATTR_NORMAL, i);
+                       fprintf(fp, "vn %f %f %f\n", v.x, v.y, v.z);
+               }
+       }
+
+       if(has_attrib(MESH_ATTR_TEXCOORD)) {
+               for(int i=0; i<(int)nverts; i++) {
+                       Vec4 v = get_attrib(MESH_ATTR_TEXCOORD, i);
+                       fprintf(fp, "vt %f %f\n", v.x, v.y);
+               }
+       }
+
+       if(is_indexed()) {
+               const unsigned int *idxptr = get_index_data();
+               int numidx = get_index_count();
+               int numtri = numidx / 3;
+               assert(numidx % 3 == 0);
+
+               for(int i=0; i<numtri; i++) {
+                       fputc('f', fp);
+                       for(int j=0; j<3; j++) {
+                               unsigned int idx = *idxptr++ + 1 + voffs;
+                               fprintf(fp, " %u/%u/%u", idx, idx, idx);
+                       }
+                       fputc('\n', fp);
+               }
+       } else {
+               int numtri = nverts / 3;
+               unsigned int idx = 1 + voffs;
+               for(int i=0; i<numtri; i++) {
+                       fputc('f', fp);
+                       for(int j=0; j<3; j++) {
+                               fprintf(fp, " %u/%u/%u", idx, idx, idx);
+                               ++idx;
+                       }
+                       fputc('\n', fp);
+               }
+       }
+       return true;
+}
+
+// ------ private member functions ------
+
+void Mesh::calc_aabb()
+{
+       // the cast is to force calling the const version which doesn't invalidate
+       if(!((const Mesh*)this)->get_attrib_data(MESH_ATTR_VERTEX)) {
+               return;
+       }
+
+       aabb.min = Vec3(FLT_MAX, FLT_MAX, FLT_MAX);
+       aabb.max = -aabb.min;
+
+       for(unsigned int i=0; i<nverts; i++) {
+               Vec4 v = get_attrib(MESH_ATTR_VERTEX, i);
+               for(int j=0; j<3; j++) {
+                       if(v[j] < aabb.min[j]) {
+                               aabb.min[j] = v[j];
+                       }
+                       if(v[j] > aabb.max[j]) {
+                               aabb.max[j] = v[j];
+                       }
+               }
+       }
+       aabb_valid = true;
+}
+
+void Mesh::calc_bsph()
+{
+       // the cast is to force calling the const version which doesn't invalidate
+       if(!((const Mesh*)this)->get_attrib_data(MESH_ATTR_VERTEX)) {
+               return;
+       }
+
+       Vec3 v;
+       bsph.center = Vec3(0, 0, 0);
+
+       // first find the center
+       for(unsigned int i=0; i<nverts; i++) {
+               v = Vec3(get_attrib(MESH_ATTR_VERTEX, i));
+               bsph.center += v;
+       }
+       bsph.center /= (float)nverts;
+
+       bsph.radius = 0.0f;
+       for(unsigned int i=0; i<nverts; i++) {
+               v = Vec3(get_attrib(MESH_ATTR_VERTEX, i));
+               float dist_sq = length_sq(v - bsph.center);
+               if(dist_sq > bsph.radius) {
+                       bsph.radius = dist_sq;
+               }
+       }
+       bsph.radius = sqrt(bsph.radius);
+
+       bsph_valid = true;
+}
+
+void Mesh::update_buffers()
+{
+       for(int i=0; i<NUM_MESH_ATTR; i++) {
+               if(has_attrib(i) && !vattr[i].vbo_valid) {
+                       glBindBuffer(GL_ARRAY_BUFFER, vattr[i].vbo);
+                       glBufferData(GL_ARRAY_BUFFER, nverts * vattr[i].nelem * sizeof(float), &vattr[i].data[0], GL_STATIC_DRAW);
+                       vattr[i].vbo_valid = true;
+               }
+       }
+       glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+       if(idata_valid && !ibo_valid) {
+               glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
+               glBufferData(GL_ELEMENT_ARRAY_BUFFER, nfaces * 3 * sizeof(unsigned int), &idata[0], GL_STATIC_DRAW);
+               ibo_valid = true;
+       }
+       glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+}
+
+void Mesh::update_wire_ibo()
+{
+       update_buffers();
+
+       if(wire_ibo_valid) {
+               return;
+       }
+
+       if(!wire_ibo) {
+               glGenBuffers(1, &wire_ibo);
+       }
+
+       int num_faces = get_poly_count();
+
+       unsigned int *wire_idxarr = new unsigned int[num_faces * 6];
+       unsigned int *dest = wire_idxarr;
+
+       if(ibo_valid) {
+               // we're dealing with an indexed mesh
+               const unsigned int *idxarr = ((const Mesh*)this)->get_index_data();
+
+               for(int i=0; i<num_faces; i++) {
+                       *dest++ = idxarr[0];
+                       *dest++ = idxarr[1];
+                       *dest++ = idxarr[1];
+                       *dest++ = idxarr[2];
+                       *dest++ = idxarr[2];
+                       *dest++ = idxarr[0];
+                       idxarr += 3;
+               }
+       } else {
+               // not an indexed mesh ...
+               for(int i=0; i<num_faces; i++) {
+                       int vidx = i * 3;
+                       *dest++ = vidx;
+                       *dest++ = vidx + 1;
+                       *dest++ = vidx + 1;
+                       *dest++ = vidx + 2;
+                       *dest++ = vidx + 2;
+                       *dest++ = vidx;
+               }
+       }
+
+       glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, wire_ibo);
+       glBufferData(GL_ELEMENT_ARRAY_BUFFER, num_faces * 6 * sizeof(unsigned int), wire_idxarr, GL_STATIC_DRAW);
+       delete [] wire_idxarr;
+       wire_ibo_valid = true;
+       glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+}
+
+
+// ------ class Triangle ------
+Triangle::Triangle()
+{
+       normal_valid = false;
+       id = -1;
+}
+
+Triangle::Triangle(const Vec3 &v0, const Vec3 &v1, const Vec3 &v2)
+{
+       v[0] = v0;
+       v[1] = v1;
+       v[2] = v2;
+       normal_valid = false;
+       id = -1;
+}
+
+Triangle::Triangle(int n, const Vec3 *varr, const unsigned int *idxarr)
+{
+       if(idxarr) {
+               v[0] = varr[idxarr[n * 3]];
+               v[1] = varr[idxarr[n * 3 + 1]];
+               v[2] = varr[idxarr[n * 3 + 2]];
+       } else {
+               v[0] = varr[n * 3];
+               v[1] = varr[n * 3 + 1];
+               v[2] = varr[n * 3 + 2];
+       }
+       normal_valid = false;
+       id = n;
+}
+
+void Triangle::calc_normal()
+{
+       normal = normalize(cross(v[1] - v[0], v[2] - v[0]));
+       normal_valid = true;
+}
+
+const Vec3 &Triangle::get_normal() const
+{
+       if(!normal_valid) {
+               ((Triangle*)this)->calc_normal();
+       }
+       return normal;
+}
+
+void Triangle::transform(const Mat4 &xform)
+{
+       v[0] = xform * v[0];
+       v[1] = xform * v[1];
+       v[2] = xform * v[2];
+       normal_valid = false;
+}
+
+void Triangle::draw() const
+{
+       Vec3 n[3];
+       n[0] = n[1] = n[2] = get_normal();
+
+       int vloc = Mesh::get_attrib_location(MESH_ATTR_VERTEX);
+       int nloc = Mesh::get_attrib_location(MESH_ATTR_NORMAL);
+
+       glEnableVertexAttribArray(vloc);
+       glVertexAttribPointer(vloc, 3, GL_FLOAT, GL_FALSE, 0, &v[0].x);
+       glVertexAttribPointer(nloc, 3, GL_FLOAT, GL_FALSE, 0, &n[0].x);
+
+       glDrawArrays(GL_TRIANGLES, 0, 3);
+
+       glDisableVertexAttribArray(vloc);
+       glDisableVertexAttribArray(nloc);
+}
+
+void Triangle::draw_wire() const
+{
+       static const int idxarr[] = {0, 1, 1, 2, 2, 0};
+       int vloc = Mesh::get_attrib_location(MESH_ATTR_VERTEX);
+
+       glEnableVertexAttribArray(vloc);
+       glVertexAttribPointer(vloc, 3, GL_FLOAT, GL_FALSE, 0, &v[0].x);
+
+       glDrawElements(GL_LINES, 6, GL_UNSIGNED_INT, idxarr);
+
+       glDisableVertexAttribArray(vloc);
+}
+
+Vec3 Triangle::calc_barycentric(const Vec3 &pos) const
+{
+       Vec3 norm = get_normal();
+
+       float area_sq = fabs(dot(cross(v[1] - v[0], v[2] - v[0]), norm));
+       if(area_sq < 1e-5) {
+               return Vec3(0, 0, 0);
+       }
+
+       float asq0 = fabs(dot(cross(v[1] - pos, v[2] - pos), norm));
+       float asq1 = fabs(dot(cross(v[2] - pos, v[0] - pos), norm));
+       float asq2 = fabs(dot(cross(v[0] - pos, v[1] - pos), norm));
+
+       return Vec3(asq0 / area_sq, asq1 / area_sq, asq2 / area_sq);
+}
+
+bool Triangle::intersect(const Ray &ray, HitPoint *hit) const
+{
+       Vec3 normal = get_normal();
+
+       float ndotdir = dot(ray.dir, normal);
+       if(fabs(ndotdir) < 1e-4) {
+               return false;
+       }
+
+       Vec3 vertdir = v[0] - ray.origin;
+       float t = dot(normal, vertdir) / ndotdir;
+
+       Vec3 pos = ray.origin + ray.dir * t;
+       Vec3 bary = calc_barycentric(pos);
+
+       if(bary.x + bary.y + bary.z > 1.00001) {
+               return false;
+       }
+
+       if(hit) {
+               hit->dist = t;
+               hit->pos = ray.origin + ray.dir * t;
+               hit->normal = normal;
+               hit->data = (void*)this;
+       }
+       return true;
+}
diff --git a/src/mesh.h b/src/mesh.h
new file mode 100644 (file)
index 0000000..8291aa7
--- /dev/null
@@ -0,0 +1,249 @@
+#ifndef MESH_H_
+#define MESH_H_
+
+#include <stdio.h>
+#include <string>
+#include <vector>
+#include "gmath/gmath.h"
+#include "geom.h"
+
+enum {
+       MESH_ATTR_VERTEX,
+       MESH_ATTR_NORMAL,
+       MESH_ATTR_TANGENT,
+       MESH_ATTR_TEXCOORD,
+       MESH_ATTR_COLOR,
+       MESH_ATTR_BONEWEIGHTS,
+       MESH_ATTR_BONEIDX,
+       MESH_ATTR_TEXCOORD2,
+
+       NUM_MESH_ATTR
+};
+
+// intersection mode flags
+enum {
+       ISECT_DEFAULT   = 0,    // default (whole mesh, all intersections)
+       ISECT_FRONT             = 1,    // front-faces only
+       ISECT_FACE              = 2,    // return intersected face pointer instead of mesh
+       ISECT_VERTICES  = 4             // return (?) TODO
+};
+
+//class XFormNode;
+
+
+class Triangle {
+public:
+       Vec3 v[3];
+       Vec3 normal;
+       bool normal_valid;
+       int id;
+
+       Triangle();
+       Triangle(const Vec3 &v0, const Vec3 &v1, const Vec3 &v2);
+       Triangle(int n, const Vec3 *varr, const unsigned int *idxarr = 0);
+
+       /// calculate normal (quite expensive)
+       void calc_normal();
+       const Vec3 &get_normal() const;
+
+       void transform(const Mat4 &xform);
+
+       void draw() const;
+       void draw_wire() const;
+
+       /// calculate barycentric coordinates of a point
+       Vec3 calc_barycentric(const Vec3 &pos) const;
+
+       bool intersect(const Ray &ray, HitPoint *hit = 0) const;
+};
+
+
+class Mesh {
+private:
+       std::string name;
+       unsigned int nverts, nfaces;
+
+       // current value for each attribute for the immedate mode
+       // interface.
+       Vec4 cur_val[NUM_MESH_ATTR];
+
+       unsigned int buffer_objects[NUM_MESH_ATTR + 1];
+
+       // vertex attribute data and buffer objects
+       struct VertexAttrib {
+               int nelem;                                      // number of elements per attribute range: [1, 4]
+               std::vector<float> data;
+               unsigned int vbo;
+               mutable bool vbo_valid;         // if this is false, the vbo needs updating from the data
+               mutable bool data_valid;        // if this is false, the data needs to be pulled from the vbo
+               //int sdr_loc;
+       } vattr[NUM_MESH_ATTR];
+
+       static int global_sdr_loc[NUM_MESH_ATTR];
+
+       //std::vector<XFormNode*> bones;        // bones affecting this mesh
+
+       // index data and buffer object
+       std::vector<unsigned int> idata;
+       unsigned int ibo;
+       mutable bool ibo_valid;
+       mutable bool idata_valid;
+
+       // index buffer object for wireframe rendering (constructed on demand)
+       unsigned int wire_ibo;
+       mutable bool wire_ibo_valid;
+
+       // axis-aligned bounding box
+       mutable AABox aabb;
+       mutable bool aabb_valid;
+
+       // bounding sphere
+       mutable Sphere bsph;
+       mutable bool bsph_valid;
+
+       // keeps the last intersected face
+       mutable Triangle hitface;
+       // keeps the last intersected vertex position
+       mutable Vec3 hitvert;
+
+       void calc_aabb();
+       void calc_bsph();
+
+       static unsigned int intersect_mode;
+       static float vertex_sel_dist;
+
+       static float vis_vecsize;
+
+       /// update the VBOs after data has changed (invalid vbo/ibo)
+       void update_buffers();
+       /// construct/update the wireframe index buffer (called from draw_wire).
+       void update_wire_ibo();
+
+       mutable int cur_sdr;
+       bool pre_draw() const;
+       void post_draw() const;
+
+
+public:
+       static bool use_custom_sdr_attr;
+
+       Mesh();
+       ~Mesh();
+
+       Mesh(const Mesh &rhs);
+       Mesh &operator =(const Mesh &rhs);
+       bool clone(const Mesh &m);
+
+       void set_name(const char *name);
+       const char *get_name() const;
+
+       bool has_attrib(int attr) const;
+       bool is_indexed() const;
+
+       // clears everything about this mesh, and returns to the newly constructed state
+       void clear();
+
+       // access the vertex attribute data
+       // if vdata == 0, space is just allocated
+       float *set_attrib_data(int attrib, int nelem, unsigned int num, const float *vdata = 0); // invalidates vbo
+       float *get_attrib_data(int attrib);     // invalidates vbo
+       const float *get_attrib_data(int attrib) const;
+
+       // simple access to any particular attribute
+       void set_attrib(int attrib, int idx, const Vec4 &v); // invalidates vbo
+       Vec4 get_attrib(int attrib, int idx) const;
+
+       int get_attrib_count(int attrib) const;
+
+       // ... same for index data
+       unsigned int *set_index_data(int num, const unsigned int *indices = 0); // invalidates ibo
+       unsigned int *get_index_data(); // invalidates ibo
+       const unsigned int *get_index_data() const;
+
+       int get_index_count() const;
+
+       void append(const Mesh &mesh);
+
+       // immediate-mode style mesh construction interface
+       void vertex(float x, float y, float z);
+       void normal(float nx, float ny, float nz);
+       void tangent(float tx, float ty, float tz);
+       void texcoord(float u, float v, float w);
+       void boneweights(float w1, float w2, float w3, float w4);
+       void boneidx(int idx1, int idx2, int idx3, int idx4);
+
+       int get_poly_count() const;
+
+       /* apply a transformation to the vertices and its inverse-transpose
+        * to the normals and tangents.
+        */
+       void apply_xform(const Mat4 &xform);
+       void apply_xform(const Mat4 &xform, const Mat4 &dir_xform);
+
+       void flip();    // both faces and normals
+       void flip_faces();
+       void flip_normals();
+
+       void explode(); // undo all vertex sharing
+
+       void calc_face_normals(); // this is only guaranteed to work on an exploded mesh
+
+       // adds a bone and returns its index
+       /*int add_bone(XFormNode *bone);
+       const XFormNode *get_bone(int idx) const;
+       int get_bones_count() const;*/
+
+       // access the shader attribute locations
+       static void set_attrib_location(int attr, int loc);
+       static int get_attrib_location(int attr);
+       static void clear_attrib_locations();
+
+       static void set_vis_vecsize(float sz);
+       static float get_vis_vecsize();
+
+       void draw() const;
+       void draw_wire() const;
+       void draw_vertices() const;
+       void draw_normals() const;
+       void draw_tangents() const;
+
+       /** get the bounding box in local space. The result will be cached, and subsequent
+        * calls will return the same box. The cache gets invalidated by any functions that can affect
+        * the vertex data (non-const variant of get_attrib_data(MESH_ATTR_VERTEX, ...) included).
+        * @{ */
+       void get_aabbox(Vec3 *vmin, Vec3 *vmax) const;
+       const AABox &get_aabbox() const;
+       /// @}
+
+       /** get the bounding sphere in local space. The result will be cached, and subsequent
+        * calls will return the same box. The cache gets invalidated by any functions that can affect
+        * the vertex data (non-const variant of get_attrib_data(MESH_ATTR_VERTEX, ...) included).
+        * @{ */
+       float get_bsphere(Vec3 *center, float *rad) const;
+       const Sphere &get_bsphere() const;
+       /// @}
+
+       static void set_intersect_mode(unsigned int mode);
+       static unsigned int get_intersect_mode();
+       static void set_vertex_select_distance(float dist);
+       static float get_vertex_select_distance();
+
+       /** Find the intersection between the mesh and a ray.
+        * XXX Brute force at the moment, not intended to be used for anything other than picking in tools.
+        *     If you intend to use it in a speed-critical part of the code, you'll *have* to optimize it!
+        */
+       bool intersect(const Ray &ray, HitPoint *hit = 0) const;
+
+       // texture coordinate manipulation
+       void texcoord_apply_xform(const Mat4 &xform);
+       void texcoord_gen_plane(const Vec3 &norm, const Vec3 &tang);
+       void texcoord_gen_box();
+       void texcoord_gen_cylinder();
+
+       bool dump(const char *fname) const;
+       bool dump(FILE *fp) const;
+       bool dump_obj(const char *fname) const;
+       bool dump_obj(FILE *fp, int voffs = 0) const;
+};
+
+#endif // MESH_H_
diff --git a/src/meshgen.cc b/src/meshgen.cc
new file mode 100644 (file)
index 0000000..7664aea
--- /dev/null
@@ -0,0 +1,883 @@
+#include <stdio.h>
+#include "meshgen.h"
+#include "mesh.h"
+
+// -------- sphere --------
+
+#define SURAD(u)       ((u) * 2.0 * M_PI)
+#define SVRAD(v)       ((v) * M_PI)
+
+static Vec3 sphvec(float theta, float phi)
+{
+       return Vec3(sin(theta) * sin(phi),
+                       cos(phi),
+                       cos(theta) * sin(phi));
+}
+
+void gen_sphere(Mesh *mesh, float rad, int usub, int vsub, float urange, float vrange)
+{
+       if(urange == 0.0 || vrange == 0.0) return;
+
+       if(usub < 4) usub = 4;
+       if(vsub < 2) vsub = 2;
+
+       int uverts = usub + 1;
+       int vverts = vsub + 1;
+
+       int num_verts = uverts * vverts;
+       int num_quads = usub * vsub;
+       int num_tri = num_quads * 2;
+
+       mesh->clear();
+       Vec3 *varr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_VERTEX, 3, num_verts, 0);
+       Vec3 *narr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_NORMAL, 3, num_verts, 0);
+       Vec3 *tarr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_TANGENT, 3, num_verts, 0);
+       Vec2 *uvarr = (Vec2*)mesh->set_attrib_data(MESH_ATTR_TEXCOORD, 2, num_verts, 0);
+       unsigned int *idxarr = mesh->set_index_data(num_tri * 3, 0);
+
+       float du = urange / (float)(uverts - 1);
+       float dv = vrange / (float)(vverts - 1);
+
+       float u = 0.0;
+       for(int i=0; i<uverts; i++) {
+               float theta = u * 2.0 * M_PI;
+
+               float v = 0.0;
+               for(int j=0; j<vverts; j++) {
+                       float phi = v * M_PI;
+
+                       Vec3 pos = sphvec(theta, phi);
+
+                       *varr++ = pos * rad;
+                       *narr++ = pos;
+                       *tarr++ = normalize(sphvec(theta + 0.1f, (float)M_PI / 2.0f) - sphvec(theta - 0.1f, (float)M_PI / 2.0f));
+                       *uvarr++ = Vec2(u / urange, v / vrange);
+
+                       if(i < usub && j < vsub) {
+                               int idx = i * vverts + j;
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + 1;
+                               *idxarr++ = idx + vverts + 1;
+
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + vverts + 1;
+                               *idxarr++ = idx + vverts;
+                       }
+
+                       v += dv;
+               }
+               u += du;
+       }
+}
+
+// ------ geosphere ------
+#define PHI            1.618034
+
+static Vec3 icosa_pt[] = {
+       Vec3(PHI, 1, 0),
+       Vec3(-PHI, 1, 0),
+       Vec3(PHI, -1, 0),
+       Vec3(-PHI, -1, 0),
+       Vec3(1, 0, PHI),
+       Vec3(1, 0, -PHI),
+       Vec3(-1, 0, PHI),
+       Vec3(-1, 0, -PHI),
+       Vec3(0, PHI, 1),
+       Vec3(0, -PHI, 1),
+       Vec3(0, PHI, -1),
+       Vec3(0, -PHI, -1)
+};
+enum { P11, P12, P13, P14, P21, P22, P23, P24, P31, P32, P33, P34 };
+static int icosa_idx[] = {
+       P11, P31, P21,
+       P11, P22, P33,
+       P13, P21, P32,
+       P13, P34, P22,
+       P12, P23, P31,
+       P12, P33, P24,
+       P14, P32, P23,
+       P14, P24, P34,
+
+       P11, P33, P31,
+       P12, P31, P33,
+       P13, P32, P34,
+       P14, P34, P32,
+
+       P21, P13, P11,
+       P22, P11, P13,
+       P23, P12, P14,
+       P24, P14, P12,
+
+       P31, P23, P21,
+       P32, P21, P23,
+       P33, P22, P24,
+       P34, P24, P22
+};
+
+static void geosphere(std::vector<Vec3> *verts, const Vec3 &v1, const Vec3 &v2, const Vec3 &v3, int iter)
+{
+       if(!iter) {
+               verts->push_back(v1);
+               verts->push_back(v2);
+               verts->push_back(v3);
+               return;
+       }
+
+       Vec3 v12 = normalize(v1 + v2);
+       Vec3 v23 = normalize(v2 + v3);
+       Vec3 v31 = normalize(v3 + v1);
+
+       geosphere(verts, v1, v12, v31, iter - 1);
+       geosphere(verts, v2, v23, v12, iter - 1);
+       geosphere(verts, v3, v31, v23, iter - 1);
+       geosphere(verts, v12, v23, v31, iter - 1);
+}
+
+void gen_geosphere(Mesh *mesh, float rad, int subdiv, bool hemi)
+{
+       int num_tri = (sizeof icosa_idx / sizeof *icosa_idx) / 3;
+
+       std::vector<Vec3> verts;
+       for(int i=0; i<num_tri; i++) {
+               Vec3 v[3];
+
+               for(int j=0; j<3; j++) {
+                       int vidx = icosa_idx[i * 3 + j];
+                       v[j] = normalize(icosa_pt[vidx]);
+               }
+
+               if(hemi && (v[0].y < 0.0 || v[1].y < 0.0 || v[2].y < 0.0)) {
+                       continue;
+               }
+
+               geosphere(&verts, v[0], v[1], v[2], subdiv);
+       }
+
+       int num_verts = (int)verts.size();
+
+       mesh->clear();
+       Vec3 *varr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_VERTEX, 3, num_verts, 0);
+       Vec3 *narr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_NORMAL, 3, num_verts, 0);
+       Vec3 *tarr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_TANGENT, 3, num_verts, 0);
+       Vec2 *uvarr = (Vec2*)mesh->set_attrib_data(MESH_ATTR_TEXCOORD, 2, num_verts, 0);
+
+       for(int i=0; i<num_verts; i++) {
+               *varr++ = verts[i] * rad;
+               *narr++ = verts[i];
+
+               float theta = atan2(verts[i].z, verts[i].x);
+               float phi = acos(verts[i].y);
+
+               *tarr++ = normalize(sphvec(theta + 0.1f, (float)M_PI / 2.0f) - sphvec(theta - 0.1f, (float)M_PI / 2.0f));
+
+               float u = 0.5 * theta / M_PI + 0.5;
+               float v = phi / M_PI;
+               *uvarr++ = Vec2(u, v);
+       }
+}
+
+// -------- torus -----------
+static Vec3 torusvec(float theta, float phi, float mr, float rr)
+{
+       theta = -theta;
+
+       float rx = -cos(phi) * rr + mr;
+       float ry = sin(phi) * rr;
+       float rz = 0.0;
+
+       float x = rx * sin(theta) + rz * cos(theta);
+       float y = ry;
+       float z = -rx * cos(theta) + rz * sin(theta);
+
+       return Vec3(x, y, z);
+}
+
+void gen_torus(Mesh *mesh, float mainrad, float ringrad, int usub, int vsub, float urange, float vrange)
+{
+       if(usub < 4) usub = 4;
+       if(vsub < 2) vsub = 2;
+
+       int uverts = usub + 1;
+       int vverts = vsub + 1;
+
+       int num_verts = uverts * vverts;
+       int num_quads = usub * vsub;
+       int num_tri = num_quads * 2;
+
+       mesh->clear();
+       Vec3 *varr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_VERTEX, 3, num_verts, 0);
+       Vec3 *narr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_NORMAL, 3, num_verts, 0);
+       Vec3 *tarr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_TANGENT, 3, num_verts, 0);
+       Vec2 *uvarr = (Vec2*)mesh->set_attrib_data(MESH_ATTR_TEXCOORD, 2, num_verts, 0);
+       unsigned int *idxarr = mesh->set_index_data(num_tri * 3, 0);
+
+       float du = urange / (float)(uverts - 1);
+       float dv = vrange / (float)(vverts - 1);
+
+       float u = 0.0;
+       for(int i=0; i<uverts; i++) {
+               float theta = u * 2.0 * M_PI;
+
+               float v = 0.0;
+               for(int j=0; j<vverts; j++) {
+                       float phi = v * 2.0 * M_PI;
+
+                       Vec3 pos = torusvec(theta, phi, mainrad, ringrad);
+                       Vec3 cent = torusvec(theta, phi, mainrad, 0.0);
+
+                       *varr++ = pos;
+                       *narr++ = (pos - cent) / ringrad;
+
+                       Vec3 pprev = torusvec(theta - 0.1f, phi, mainrad, ringrad);
+                       Vec3 pnext = torusvec(theta + 0.1f, phi, mainrad, ringrad);
+
+                       *tarr++ = normalize(pnext - pprev);
+                       *uvarr++ = Vec2(u * urange, v * vrange);
+
+                       if(i < usub && j < vsub) {
+                               int idx = i * vverts + j;
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + 1;
+                               *idxarr++ = idx + vverts + 1;
+
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + vverts + 1;
+                               *idxarr++ = idx + vverts;
+                       }
+
+                       v += dv;
+               }
+               u += du;
+       }
+}
+
+
+// -------- cylinder --------
+
+static Vec3 cylvec(float theta, float height)
+{
+       return Vec3(sin(theta), height, cos(theta));
+}
+
+void gen_cylinder(Mesh *mesh, float rad, float height, int usub, int vsub, int capsub, float urange, float vrange)
+{
+       if(usub < 4) usub = 4;
+       if(vsub < 1) vsub = 1;
+
+       int uverts = usub + 1;
+       int vverts = vsub + 1;
+
+       int num_body_verts = uverts * vverts;
+       int num_body_quads = usub * vsub;
+       int num_body_tri = num_body_quads * 2;
+
+       int capvverts = capsub ? capsub + 1 : 0;
+       int num_cap_verts = uverts * capvverts;
+       int num_cap_quads = usub * capsub;
+       int num_cap_tri = num_cap_quads * 2;
+
+       int num_verts = num_body_verts + num_cap_verts * 2;
+       int num_tri = num_body_tri + num_cap_tri * 2;
+
+       mesh->clear();
+       Vec3 *varr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_VERTEX, 3, num_verts, 0);
+       Vec3 *narr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_NORMAL, 3, num_verts, 0);
+       Vec3 *tarr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_TANGENT, 3, num_verts, 0);
+       Vec2 *uvarr = (Vec2*)mesh->set_attrib_data(MESH_ATTR_TEXCOORD, 2, num_verts, 0);
+       unsigned int *idxarr = mesh->set_index_data(num_tri * 3, 0);
+
+       float du = urange / (float)(uverts - 1);
+       float dv = vrange / (float)(vverts - 1);
+
+       float u = 0.0;
+       for(int i=0; i<uverts; i++) {
+               float theta = SURAD(u);
+
+               float v = 0.0;
+               for(int j=0; j<vverts; j++) {
+                       float y = (v - 0.5) * height;
+                       Vec3 pos = cylvec(theta, y);
+
+                       *varr++ = Vec3(pos.x * rad, pos.y, pos.z * rad);
+                       *narr++ = Vec3(pos.x, 0.0, pos.z);
+                       *tarr++ = normalize(cylvec(theta + 0.1, 0.0) - cylvec(theta - 0.1, 0.0));
+                       *uvarr++ = Vec2(u * urange, v * vrange);
+
+                       if(i < usub && j < vsub) {
+                               int idx = i * vverts + j;
+
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + vverts + 1;
+                               *idxarr++ = idx + 1;
+
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + vverts;
+                               *idxarr++ = idx + vverts + 1;
+                       }
+
+                       v += dv;
+               }
+               u += du;
+       }
+
+
+       // now the cap!
+       if(!capsub) {
+               return;
+       }
+
+       dv = 1.0 / (float)(capvverts - 1);
+
+       u = 0.0;
+       for(int i=0; i<uverts; i++) {
+               float theta = SURAD(u);
+
+               float v = 0.0;
+               for(int j=0; j<capvverts; j++) {
+                       float r = v * rad;
+
+                       Vec3 pos = cylvec(theta, height / 2.0) * r;
+                       pos.y = height / 2.0;
+                       Vec3 tang = normalize(cylvec(theta + 0.1, 0.0) - cylvec(theta - 0.1, 0.0));
+
+                       *varr++ = pos;
+                       *narr++ = Vec3(0, 1, 0);
+                       *tarr++ = tang;
+                       *uvarr++ = Vec2(u * urange, v);
+
+                       pos.y = -height / 2.0;
+                       *varr++ = pos;
+                       *narr++ = Vec3(0, -1, 0);
+                       *tarr++ = -tang;
+                       *uvarr++ = Vec2(u * urange, v);
+
+                       if(i < usub && j < capsub) {
+                               unsigned int idx = num_body_verts + (i * capvverts + j) * 2;
+
+                               unsigned int vidx[4] = {
+                                       idx,
+                                       idx + capvverts * 2,
+                                       idx + (capvverts + 1) * 2,
+                                       idx + 2
+                               };
+
+                               *idxarr++ = vidx[0];
+                               *idxarr++ = vidx[2];
+                               *idxarr++ = vidx[1];
+                               *idxarr++ = vidx[0];
+                               *idxarr++ = vidx[3];
+                               *idxarr++ = vidx[2];
+
+                               *idxarr++ = vidx[0] + 1;
+                               *idxarr++ = vidx[1] + 1;
+                               *idxarr++ = vidx[2] + 1;
+                               *idxarr++ = vidx[0] + 1;
+                               *idxarr++ = vidx[2] + 1;
+                               *idxarr++ = vidx[3] + 1;
+                       }
+
+                       v += dv;
+               }
+               u += du;
+       }
+}
+
+// -------- cone --------
+
+static Vec3 conevec(float theta, float y, float height)
+{
+       float scale = 1.0 - y / height;
+       return Vec3(sin(theta) * scale, y, cos(theta) * scale);
+}
+
+void gen_cone(Mesh *mesh, float rad, float height, int usub, int vsub, int capsub, float urange, float vrange)
+{
+       if(usub < 4) usub = 4;
+       if(vsub < 1) vsub = 1;
+
+       int uverts = usub + 1;
+       int vverts = vsub + 1;
+
+       int num_body_verts = uverts * vverts;
+       int num_body_quads = usub * vsub;
+       int num_body_tri = num_body_quads * 2;
+
+       int capvverts = capsub ? capsub + 1 : 0;
+       int num_cap_verts = uverts * capvverts;
+       int num_cap_quads = usub * capsub;
+       int num_cap_tri = num_cap_quads * 2;
+
+       int num_verts = num_body_verts + num_cap_verts;
+       int num_tri = num_body_tri + num_cap_tri;
+
+       mesh->clear();
+       Vec3 *varr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_VERTEX, 3, num_verts, 0);
+       Vec3 *narr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_NORMAL, 3, num_verts, 0);
+       Vec3 *tarr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_TANGENT, 3, num_verts, 0);
+       Vec2 *uvarr = (Vec2*)mesh->set_attrib_data(MESH_ATTR_TEXCOORD, 2, num_verts, 0);
+       unsigned int *idxarr = mesh->set_index_data(num_tri * 3, 0);
+
+       float du = urange / (float)(uverts - 1);
+       float dv = vrange / (float)(vverts - 1);
+
+       float u = 0.0;
+       for(int i=0; i<uverts; i++) {
+               float theta = SURAD(u);
+
+               float v = 0.0;
+               for(int j=0; j<vverts; j++) {
+                       float y = v * height;
+                       Vec3 pos = conevec(theta, y, height);
+
+                       Vec3 tang = normalize(conevec(theta + 0.1, 0.0, height) - conevec(theta - 0.1, 0.0, height));
+                       Vec3 bitang = normalize(conevec(theta, y + 0.1, height) - pos);
+
+                       *varr++ = Vec3(pos.x * rad, pos.y, pos.z * rad);
+                       *narr++ = cross(tang, bitang);
+                       *tarr++ = tang;
+                       *uvarr++ = Vec2(u * urange, v * vrange);
+
+                       if(i < usub && j < vsub) {
+                               int idx = i * vverts + j;
+
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + vverts + 1;
+                               *idxarr++ = idx + 1;
+
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + vverts;
+                               *idxarr++ = idx + vverts + 1;
+                       }
+
+                       v += dv;
+               }
+               u += du;
+       }
+
+
+       // now the bottom cap!
+       if(!capsub) {
+               return;
+       }
+
+       dv = 1.0 / (float)(capvverts - 1);
+
+       u = 0.0;
+       for(int i=0; i<uverts; i++) {
+               float theta = SURAD(u);
+
+               float v = 0.0;
+               for(int j=0; j<capvverts; j++) {
+                       float r = v * rad;
+
+                       Vec3 pos = conevec(theta, 0.0, height) * r;
+                       Vec3 tang = normalize(cylvec(theta + 0.1, 0.0) - cylvec(theta - 0.1, 0.0));
+
+                       *varr++ = pos;
+                       *narr++ = Vec3(0, -1, 0);
+                       *tarr++ = tang;
+                       *uvarr++ = Vec2(u * urange, v);
+
+                       if(i < usub && j < capsub) {
+                               unsigned int idx = num_body_verts + i * capvverts + j;
+
+                               unsigned int vidx[4] = {
+                                       idx,
+                                       idx + capvverts,
+                                       idx + (capvverts + 1),
+                                       idx + 1
+                               };
+
+                               *idxarr++ = vidx[0];
+                               *idxarr++ = vidx[1];
+                               *idxarr++ = vidx[2];
+                               *idxarr++ = vidx[0];
+                               *idxarr++ = vidx[2];
+                               *idxarr++ = vidx[3];
+                       }
+
+                       v += dv;
+               }
+               u += du;
+       }
+}
+
+
+// -------- plane --------
+
+void gen_plane(Mesh *mesh, float width, float height, int usub, int vsub)
+{
+       gen_heightmap(mesh, width, height, usub, vsub, 0);
+}
+
+
+// ----- heightmap ------
+
+void gen_heightmap(Mesh *mesh, float width, float height, int usub, int vsub, float (*hf)(float, float, void*), void *hfdata)
+{
+       if(usub < 1) usub = 1;
+       if(vsub < 1) vsub = 1;
+
+       mesh->clear();
+
+       int uverts = usub + 1;
+       int vverts = vsub + 1;
+       int num_verts = uverts * vverts;
+
+       int num_quads = usub * vsub;
+       int num_tri = num_quads * 2;
+
+       Vec3 *varr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_VERTEX, 3, num_verts, 0);
+       Vec3 *narr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_NORMAL, 3, num_verts, 0);
+       Vec3 *tarr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_TANGENT, 3, num_verts, 0);
+       Vec2 *uvarr = (Vec2*)mesh->set_attrib_data(MESH_ATTR_TEXCOORD, 2, num_verts, 0);
+       unsigned int *idxarr = mesh->set_index_data(num_tri * 3, 0);
+
+       float du = 1.0 / (float)usub;
+       float dv = 1.0 / (float)vsub;
+
+       float u = 0.0;
+       for(int i=0; i<uverts; i++) {
+               float v = 0.0;
+               for(int j=0; j<vverts; j++) {
+                       float x = (u - 0.5) * width;
+                       float y = (v - 0.5) * height;
+                       float z = hf ? hf(u, v, hfdata) : 0.0;
+
+                       Vec3 normal = Vec3(0, 0, 1);
+                       if(hf) {
+                               float u1z = hf(u + du, v, hfdata);
+                               float v1z = hf(u, v + dv, hfdata);
+
+                               Vec3 tang = Vec3(du * width, 0, u1z - z);
+                               Vec3 bitan = Vec3(0, dv * height, v1z - z);
+                               normal = normalize(cross(tang, bitan));
+                       }
+
+                       *varr++ = Vec3(x, y, z);
+                       *narr++ = normal;
+                       *tarr++ = Vec3(1, 0, 0);
+                       *uvarr++ = Vec2(u, v);
+
+                       if(i < usub && j < vsub) {
+                               int idx = i * vverts + j;
+
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + vverts + 1;
+                               *idxarr++ = idx + 1;
+
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + vverts;
+                               *idxarr++ = idx + vverts + 1;
+                       }
+
+                       v += dv;
+               }
+               u += du;
+       }
+}
+
+// ----- box ------
+void gen_box(Mesh *mesh, float xsz, float ysz, float zsz, int usub, int vsub)
+{
+       static const float face_angles[][2] = {
+               {0, 0},
+               {M_PI / 2.0, 0},
+               {M_PI, 0},
+               {3.0 * M_PI / 2.0, 0},
+               {0, M_PI / 2.0},
+               {0, -M_PI / 2.0}
+       };
+
+       if(usub < 1) usub = 1;
+       if(vsub < 1) vsub = 1;
+
+       mesh->clear();
+
+       for(int i=0; i<6; i++) {
+               Mat4 xform, dir_xform;
+               Mesh m;
+
+               gen_plane(&m, 1, 1, usub, vsub);
+               xform.translate(Vec3(0, 0, 0.5));
+               xform.rotate(Vec3(face_angles[i][1], face_angles[i][0], 0));
+               dir_xform = xform;
+               m.apply_xform(xform, dir_xform);
+
+               mesh->append(m);
+       }
+
+       Mat4 scale;
+       scale.scaling(xsz, ysz, zsz);
+       mesh->apply_xform(scale, Mat4::identity);
+}
+
+/*
+void gen_box(Mesh *mesh, float xsz, float ysz, float zsz)
+{
+       mesh->clear();
+
+       const int num_faces = 6;
+       int num_verts = num_faces * 4;
+       int num_tri = num_faces * 2;
+
+       float x = xsz / 2.0;
+       float y = ysz / 2.0;
+       float z = zsz / 2.0;
+
+       Vec3 *varr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_VERTEX, 3, num_verts, 0);
+       Vec3 *narr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_NORMAL, 3, num_verts, 0);
+       Vec3 *tarr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_TANGENT, 3, num_verts, 0);
+       Vec2 *uvarr = (Vec2*)mesh->set_attrib_data(MESH_ATTR_TEXCOORD, 2, num_verts, 0);
+       unsigned int *idxarr = mesh->set_index_data(num_tri * 3, 0);
+
+       static const Vec2 uv[] = { Vec2(0, 0), Vec2(1, 0), Vec2(1, 1), Vec2(0, 1) };
+
+       // front
+       for(int i=0; i<4; i++) {
+               *narr++ = Vec3(0, 0, 1);
+               *tarr++ = Vec3(1, 0, 0);
+               *uvarr++ = uv[i];
+       }
+       *varr++ = Vec3(-x, -y, z);
+       *varr++ = Vec3(x, -y, z);
+       *varr++ = Vec3(x, y, z);
+       *varr++ = Vec3(-x, y, z);
+       // right
+       for(int i=0; i<4; i++) {
+               *narr++ = Vec3(1, 0, 0);
+               *tarr++ = Vec3(0, 0, -1);
+               *uvarr++ = uv[i];
+       }
+       *varr++ = Vec3(x, -y, z);
+       *varr++ = Vec3(x, -y, -z);
+       *varr++ = Vec3(x, y, -z);
+       *varr++ = Vec3(x, y, z);
+       // back
+       for(int i=0; i<4; i++) {
+               *narr++ = Vec3(0, 0, -1);
+               *tarr++ = Vec3(-1, 0, 0);
+               *uvarr++ = uv[i];
+       }
+       *varr++ = Vec3(x, -y, -z);
+       *varr++ = Vec3(-x, -y, -z);
+       *varr++ = Vec3(-x, y, -z);
+       *varr++ = Vec3(x, y, -z);
+       // left
+       for(int i=0; i<4; i++) {
+               *narr++ = Vec3(-1, 0, 0);
+               *tarr++ = Vec3(0, 0, 1);
+               *uvarr++ = uv[i];
+       }
+       *varr++ = Vec3(-x, -y, -z);
+       *varr++ = Vec3(-x, -y, z);
+       *varr++ = Vec3(-x, y, z);
+       *varr++ = Vec3(-x, y, -z);
+       // top
+       for(int i=0; i<4; i++) {
+               *narr++ = Vec3(0, 1, 0);
+               *tarr++ = Vec3(1, 0, 0);
+               *uvarr++ = uv[i];
+       }
+       *varr++ = Vec3(-x, y, z);
+       *varr++ = Vec3(x, y, z);
+       *varr++ = Vec3(x, y, -z);
+       *varr++ = Vec3(-x, y, -z);
+       // bottom
+       for(int i=0; i<4; i++) {
+               *narr++ = Vec3(0, -1, 0);
+               *tarr++ = Vec3(1, 0, 0);
+               *uvarr++ = uv[i];
+       }
+       *varr++ = Vec3(-x, -y, -z);
+       *varr++ = Vec3(x, -y, -z);
+       *varr++ = Vec3(x, -y, z);
+       *varr++ = Vec3(-x, -y, z);
+
+       // index array
+       static const int faceidx[] = {0, 1, 2, 0, 2, 3};
+       for(int i=0; i<num_faces; i++) {
+               for(int j=0; j<6; j++) {
+                       *idxarr++ = faceidx[j] + i * 4;
+               }
+       }
+}
+*/
+
+static inline Vec3 rev_vert(float u, float v, Vec2 (*rf)(float, float, void*), void *cls)
+{
+       Vec2 pos = rf(u, v, cls);
+
+       float angle = u * 2.0 * M_PI;
+       float x = pos.x * cos(angle);
+       float y = pos.y;
+       float z = pos.x * sin(angle);
+
+       return Vec3(x, y, z);
+}
+
+// ------ surface of revolution -------
+void gen_revol(Mesh *mesh, int usub, int vsub, Vec2 (*rfunc)(float, float, void*), void *cls)
+{
+       gen_revol(mesh, usub, vsub, rfunc, 0, cls);
+}
+
+void gen_revol(Mesh *mesh, int usub, int vsub, Vec2 (*rfunc)(float, float, void*),
+               Vec2 (*nfunc)(float, float, void*), void *cls)
+{
+       if(!rfunc) return;
+       if(usub < 3) usub = 3;
+       if(vsub < 1) vsub = 1;
+
+       mesh->clear();
+
+       int uverts = usub + 1;
+       int vverts = vsub + 1;
+       int num_verts = uverts * vverts;
+
+       int num_quads = usub * vsub;
+       int num_tri = num_quads * 2;
+
+       Vec3 *varr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_VERTEX, 3, num_verts, 0);
+       Vec3 *narr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_NORMAL, 3, num_verts, 0);
+       Vec3 *tarr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_TANGENT, 3, num_verts, 0);
+       Vec2 *uvarr = (Vec2*)mesh->set_attrib_data(MESH_ATTR_TEXCOORD, 2, num_verts, 0);
+       unsigned int *idxarr = mesh->set_index_data(num_tri * 3, 0);
+
+       float du = 1.0 / (float)(uverts - 1);
+       float dv = 1.0 / (float)(vverts - 1);
+
+       float u = 0.0;
+       for(int i=0; i<uverts; i++) {
+               float v = 0.0;
+               for(int j=0; j<vverts; j++) {
+                       Vec3 pos = rev_vert(u, v, rfunc, cls);
+
+                       Vec3 nextu = rev_vert(fmod(u + du, 1.0), v, rfunc, cls);
+                       Vec3 tang = nextu - pos;
+                       if(length_sq(tang) < 1e-6) {
+                               float new_v = v > 0.5 ? v - dv * 0.25 : v + dv * 0.25;
+                               nextu = rev_vert(fmod(u + du, 1.0), new_v, rfunc, cls);
+                               tang = nextu - pos;
+                       }
+
+                       Vec3 normal;
+                       if(nfunc) {
+                               normal = rev_vert(u, v, nfunc, cls);
+                       } else {
+                               Vec3 nextv = rev_vert(u, v + dv, rfunc, cls);
+                               Vec3 bitan = nextv - pos;
+                               if(length_sq(bitan) < 1e-6) {
+                                       nextv = rev_vert(u, v - dv, rfunc, cls);
+                                       bitan = pos - nextv;
+                               }
+
+                               normal = cross(tang, bitan);
+                       }
+
+                       *varr++ = pos;
+                       *narr++ = normalize(normal);
+                       *tarr++ = normalize(tang);
+                       *uvarr++ = Vec2(u, v);
+
+                       if(i < usub && j < vsub) {
+                               int idx = i * vverts + j;
+
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + vverts + 1;
+                               *idxarr++ = idx + 1;
+
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + vverts;
+                               *idxarr++ = idx + vverts + 1;
+                       }
+
+                       v += dv;
+               }
+               u += du;
+       }
+}
+
+
+static inline Vec3 sweep_vert(float u, float v, float height, Vec2 (*sf)(float, float, void*), void *cls)
+{
+       Vec2 pos = sf(u, v, cls);
+
+       float x = pos.x;
+       float y = v * height;
+       float z = pos.y;
+
+       return Vec3(x, y, z);
+}
+
+// ---- sweep shape along a path ----
+void gen_sweep(Mesh *mesh, float height, int usub, int vsub, Vec2 (*sfunc)(float, float, void*), void *cls)
+{
+       if(!sfunc) return;
+       if(usub < 3) usub = 3;
+       if(vsub < 1) vsub = 1;
+
+       mesh->clear();
+
+       int uverts = usub + 1;
+       int vverts = vsub + 1;
+       int num_verts = uverts * vverts;
+
+       int num_quads = usub * vsub;
+       int num_tri = num_quads * 2;
+
+       Vec3 *varr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_VERTEX, 3, num_verts, 0);
+       Vec3 *narr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_NORMAL, 3, num_verts, 0);
+       Vec3 *tarr = (Vec3*)mesh->set_attrib_data(MESH_ATTR_TANGENT, 3, num_verts, 0);
+       Vec2 *uvarr = (Vec2*)mesh->set_attrib_data(MESH_ATTR_TEXCOORD, 2, num_verts, 0);
+       unsigned int *idxarr = mesh->set_index_data(num_tri * 3, 0);
+
+       float du = 1.0 / (float)(uverts - 1);
+       float dv = 1.0 / (float)(vverts - 1);
+
+       float u = 0.0;
+       for(int i=0; i<uverts; i++) {
+               float v = 0.0;
+               for(int j=0; j<vverts; j++) {
+                       Vec3 pos = sweep_vert(u, v, height, sfunc, cls);
+
+                       Vec3 nextu = sweep_vert(fmod(u + du, 1.0), v, height, sfunc, cls);
+                       Vec3 tang = nextu - pos;
+                       if(length_sq(tang) < 1e-6) {
+                               float new_v = v > 0.5 ? v - dv * 0.25 : v + dv * 0.25;
+                               nextu = sweep_vert(fmod(u + du, 1.0), new_v, height, sfunc, cls);
+                               tang = nextu - pos;
+                       }
+
+                       Vec3 normal;
+                       Vec3 nextv = sweep_vert(u, v + dv, height, sfunc, cls);
+                       Vec3 bitan = nextv - pos;
+                       if(length_sq(bitan) < 1e-6) {
+                               nextv = sweep_vert(u, v - dv, height, sfunc, cls);
+                               bitan = pos - nextv;
+                       }
+
+                       normal = cross(tang, bitan);
+
+                       *varr++ = pos;
+                       *narr++ = normalize(normal);
+                       *tarr++ = normalize(tang);
+                       *uvarr++ = Vec2(u, v);
+
+                       if(i < usub && j < vsub) {
+                               int idx = i * vverts + j;
+
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + vverts + 1;
+                               *idxarr++ = idx + 1;
+
+                               *idxarr++ = idx;
+                               *idxarr++ = idx + vverts;
+                               *idxarr++ = idx + vverts + 1;
+                       }
+
+                       v += dv;
+               }
+               u += du;
+       }
+}
diff --git a/src/meshgen.h b/src/meshgen.h
new file mode 100644 (file)
index 0000000..f154c24
--- /dev/null
@@ -0,0 +1,23 @@
+#ifndef MESHGEN_H_
+#define MESHGEN_H_
+
+#include "gmath/gmath.h"
+
+class Mesh;
+
+void gen_sphere(Mesh *mesh, float rad, int usub, int vsub, float urange = 1.0, float vrange = 1.0);
+void gen_geosphere(Mesh *mesh, float rad, int subdiv, bool hemi = false);
+void gen_torus(Mesh *mesh, float mainrad, float ringrad, int usub, int vsub, float urange = 1.0, float vrange = 1.0);
+void gen_cylinder(Mesh *mesh, float rad, float height, int usub, int vsub, int capsub = 0, float urange = 1.0, float vrange = 1.0);
+void gen_cone(Mesh *mesh, float rad, float height, int usub, int vsub, int capsub = 0, float urange = 1.0, float vrange = 1.0);
+void gen_plane(Mesh *mesh, float width, float height, int usub = 1, int vsub = 1);
+void gen_heightmap(Mesh *mesh, float width, float height, int usub, int vsub, float (*hf)(float, float, void*), void *hfdata = 0);
+void gen_box(Mesh *mesh, float xsz, float ysz, float zsz, int usub = 1, int vsub = 1);
+
+void gen_revol(Mesh *mesh, int usub, int vsub, Vec2 (*rfunc)(float, float, void*), void *cls = 0);
+void gen_revol(Mesh *mesh, int usub, int vsub, Vec2 (*rfunc)(float, float, void*), Vec2 (*nfunc)(float, float, void*), void *cls);
+
+/* callback args: (float u, float v, void *cls) -> Vec2 XZ offset u,v in [0, 1] */
+void gen_sweep(Mesh *mesh, float height, int usub, int vsub, Vec2 (*sfunc)(float, float, void*), void *cls = 0);
+
+#endif // MESHGEN_H_
index 5c3ff44..06c637b 100644 (file)
@@ -18,6 +18,9 @@ int init_opengl(void)
        if(GLEW_EXT_texture_filter_anisotropic) {
                glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &glcaps.max_aniso);
        }
        if(GLEW_EXT_texture_filter_anisotropic) {
                glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &glcaps.max_aniso);
        }
+       if(GLEW_ARB_tessellation_shader) {
+               glGetIntegerv(GL_MAX_TESS_GEN_LEVEL, &glcaps.max_tess_level);
+       }
 
        glcaps.debug = GLEW_ARB_debug_output;
 
 
        glcaps.debug = GLEW_ARB_debug_output;
 
index 397cd38..b338cd9 100644 (file)
@@ -6,6 +6,7 @@
 struct GLCaps {
        int debug;      /* ARB_debug_output */
        int max_aniso;
 struct GLCaps {
        int debug;      /* ARB_debug_output */
        int max_aniso;
+       int max_tess_level;
 };
 
 extern struct GLCaps glcaps;
 };
 
 extern struct GLCaps glcaps;