Просмотр исходного кода

voxel render 0.78; compile as C++, fix bad #else

Sean Barrett 10 лет назад
Родитель
Сommit
cd1d05b389
3 измененных файлов с 1415 добавлено и 1455 удалено
  1. 1 1
      README.md
  2. 1411 1454
      stb_voxel_render.h
  3. 3 0
      tests/test_cpp_compilation.cpp

+ 1 - 1
README.md

@@ -13,7 +13,7 @@ library    | lastest version | category | description
 **stb_rect_pack.h** | 0.05 | graphics | simple 2D rectangle packer with decent quality
 **stretchy_buffer.h** | 1.01 | utility | typesafe dynamic array for C (i.e. approximation to vector<>), doesn't compile as C++
 **stb_textedit.h** | 1.5 | UI | guts of a text editor for games etc implementing them from scratch
-**stb_voxel_render.h** | 0.77 | 3D&nbsp;graphics | Minecraft-esque voxel rendering "engine" with many more features
+**stb_voxel_render.h** | 0.78 | 3D&nbsp;graphics | Minecraft-esque voxel rendering "engine" with many more features
 **stb_dxt.h** | 1.04 | 3D&nbsp;graphics | Fabian "ryg" Giesen's real-time DXT compressor
 **stb_perlin.h** | 0.2 | 3D&nbsp;graphics | revised Perlin noise (3D input, 1D output)
 **stb_easy_font.h** | 0.5 | 3D&nbsp;graphics | quick-and-dirty easy-to-deploy bitmap font for printing frame rate, etc

+ 1411 - 1454
stb_voxel_render.h

@@ -1,4 +1,4 @@
-// stb_voxel_render.h - v0.77 - Sean Barrett, 2015 - public domain
+// stb_voxel_render.h - v0.78 - Sean Barrett, 2015 - public domain
 //
 // This library helps render large-scale "voxel" worlds for games,
 // in this case, one with blocks that can have textures and that
@@ -72,7 +72,9 @@
 //   - indexed-by-texture-#2-id blend mode (alpha composite or modulate/multiply);
 //     the first is good for decals, the second for detail textures, "light maps",
 //     etc; both modes are controlled by texture #2's alpha, scaled by the
-//     per-vertex texture crossfade and the per-face color (if enabled on texture #2)
+//     per-vertex texture crossfade and the per-face color (if enabled on texture #2);
+//     modulate/multiply multiplies by an extra factor of 2.0 so that if you
+//     make detail maps whose average brightness is 0.5 everything works nicely.
 //
 //   - ambient lighting: half-lambert directional plus constant, all scaled by vertex ao
 //   - face can be fullbright (emissive), controlled by per-face color
@@ -168,10 +170,11 @@
 //
 //   Features             Porting            Bugfixes & Warnings
 //  Sean Barrett                          github:r-leyh   Jesus Fernandez
-//                                        Miguel Lechon
+//                                        Miguel Lechon   github:Arbeiterunfallversicherungsgesetz
 //
 // VERSION HISTORY
 //
+//   0.78   bad "#else", compile as C++
 //   0.77   documentation tweaks, rename config var to STB_VOXEL_RENDER_STATIC
 //   0.76   typos, signed/unsigned shader issue, more documentation
 //   0.75   initial release
@@ -1164,7 +1167,7 @@ struct stbvox_mesh_maker
    typedef stbvox_uint16 stbvox_mesh_vertex;
    #define stbvox_vertex_encode(x,y,z,ao,texlerp) \
       ((stbvox_uint16) ((x)+((z)<<6))+((ao)<<10))
-#else defined(STBVOX_ICONFIG_VERTEX_8)
+#elif defined(STBVOX_ICONFIG_VERTEX_8)
    typedef stbvox_uint8 stbvox_mesh_vertex;
    #define stbvox_vertex_encode(x,y,z,ao,texlerp) \
       ((stbvox_uint8) ((z)+((ao)<<6))
@@ -1255,11 +1258,117 @@ enum
    STBVF_count,
 };
 
-// get opposite-facing normal & texgen for opposite face, used to map up-facing vheight data to down-facing data
-static unsigned char stbvox_reverse_face[STBVF_count];
-static float stbvox_default_texgen[2][32][3];
-static float stbvox_default_normals[32][3];
-static float stbvox_default_texscale[128][4];
+/////////////////////////////////////////////////////////////////////////////
+//
+//    tables -- i'd prefer if these were at the end of the file, but: C++
+//
+
+static float stbvox_default_texgen[2][32][3] =
+{
+   { {  0, 1,0 }, { 0, 0, 1 }, {  0,-1,0 }, { 0, 0,-1 },
+     { -1, 0,0 }, { 0, 0, 1 }, {  1, 0,0 }, { 0, 0,-1 },
+     {  0,-1,0 }, { 0, 0, 1 }, {  0, 1,0 }, { 0, 0,-1 },
+     {  1, 0,0 }, { 0, 0, 1 }, { -1, 0,0 }, { 0, 0,-1 },
+
+     {  1, 0,0 }, { 0, 1, 0 }, { -1, 0,0 }, { 0,-1, 0 },
+     { -1, 0,0 }, { 0,-1, 0 }, {  1, 0,0 }, { 0, 1, 0 },
+     {  1, 0,0 }, { 0, 1, 0 }, { -1, 0,0 }, { 0,-1, 0 },
+     { -1, 0,0 }, { 0,-1, 0 }, {  1, 0,0 }, { 0, 1, 0 },
+   },
+   { { 0, 0,-1 }, {  0, 1,0 }, { 0, 0, 1 }, {  0,-1,0 },
+     { 0, 0,-1 }, { -1, 0,0 }, { 0, 0, 1 }, {  1, 0,0 },
+     { 0, 0,-1 }, {  0,-1,0 }, { 0, 0, 1 }, {  0, 1,0 },
+     { 0, 0,-1 }, {  1, 0,0 }, { 0, 0, 1 }, { -1, 0,0 },
+
+     { 0,-1, 0 }, {  1, 0,0 }, { 0, 1, 0 }, { -1, 0,0 },
+     { 0, 1, 0 }, { -1, 0,0 }, { 0,-1, 0 }, {  1, 0,0 },
+     { 0,-1, 0 }, {  1, 0,0 }, { 0, 1, 0 }, { -1, 0,0 },
+     { 0, 1, 0 }, { -1, 0,0 }, { 0,-1, 0 }, {  1, 0,0 },
+   },
+};
+
+#define STBVOX_RSQRT2   0.7071067811865f
+#define STBVOX_RSQRT3   0.5773502691896f
+
+static float stbvox_default_normals[32][3] =
+{
+   { 1,0,0 },  // east
+   { 0,1,0 },  // north
+   { -1,0,0 }, // west
+   { 0,-1,0 }, // south
+   { 0,0,1 },  // up
+   { 0,0,-1 }, // down
+   {  STBVOX_RSQRT2,0, STBVOX_RSQRT2 }, // east & up
+   {  STBVOX_RSQRT2,0, -STBVOX_RSQRT2 }, // east & down
+
+   {  STBVOX_RSQRT2,0, STBVOX_RSQRT2 }, // east & up
+   { 0, STBVOX_RSQRT2, STBVOX_RSQRT2 }, // north & up
+   { -STBVOX_RSQRT2,0, STBVOX_RSQRT2 }, // west & up
+   { 0,-STBVOX_RSQRT2, STBVOX_RSQRT2 }, // south & up
+   {  STBVOX_RSQRT3, STBVOX_RSQRT3, STBVOX_RSQRT3 }, // ne & up
+   {  STBVOX_RSQRT3, STBVOX_RSQRT3,-STBVOX_RSQRT3 }, // ne & down
+   { 0, STBVOX_RSQRT2, STBVOX_RSQRT2 }, // north & up
+   { 0, STBVOX_RSQRT2, -STBVOX_RSQRT2 }, // north & down
+
+   {  STBVOX_RSQRT2,0, -STBVOX_RSQRT2 }, // east & down
+   { 0, STBVOX_RSQRT2, -STBVOX_RSQRT2 }, // north & down
+   { -STBVOX_RSQRT2,0, -STBVOX_RSQRT2 }, // west & down
+   { 0,-STBVOX_RSQRT2, -STBVOX_RSQRT2 }, // south & down
+   { -STBVOX_RSQRT3, STBVOX_RSQRT3, STBVOX_RSQRT3 }, // NW & up
+   { -STBVOX_RSQRT3, STBVOX_RSQRT3,-STBVOX_RSQRT3 }, // NW & down
+   { -STBVOX_RSQRT2,0, STBVOX_RSQRT2 }, // west & up
+   { -STBVOX_RSQRT2,0, -STBVOX_RSQRT2 }, // west & down
+
+   {  STBVOX_RSQRT3, STBVOX_RSQRT3,STBVOX_RSQRT3 }, // NE & up crossed
+   { -STBVOX_RSQRT3, STBVOX_RSQRT3,STBVOX_RSQRT3 }, // NW & up crossed
+   { -STBVOX_RSQRT3,-STBVOX_RSQRT3,STBVOX_RSQRT3 }, // SW & up crossed
+   {  STBVOX_RSQRT3,-STBVOX_RSQRT3,STBVOX_RSQRT3 }, // SE & up crossed
+   { -STBVOX_RSQRT3,-STBVOX_RSQRT3, STBVOX_RSQRT3 }, // SW & up
+   { -STBVOX_RSQRT3,-STBVOX_RSQRT3,-STBVOX_RSQRT3 }, // SW & up
+   { 0,-STBVOX_RSQRT2, STBVOX_RSQRT2 }, // south & up
+   { 0,-STBVOX_RSQRT2, -STBVOX_RSQRT2 }, // south & down
+};
+
+static float stbvox_default_texscale[128][4] =
+{
+   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
+   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
+   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
+   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
+   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
+   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
+   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
+   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
+   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
+   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
+   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
+   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
+   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
+   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
+   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
+   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
+};
+
+static unsigned char stbvox_default_palette_compact[64][3] =
+{
+   { 255,255,255 }, { 238,238,238 }, { 221,221,221 }, { 204,204,204 },
+   { 187,187,187 }, { 170,170,170 }, { 153,153,153 }, { 136,136,136 },
+   { 119,119,119 }, { 102,102,102 }, {  85, 85, 85 }, {  68, 68, 68 },
+   {  51, 51, 51 }, {  34, 34, 34 }, {  17, 17, 17 }, {   0,  0,  0 },
+   { 255,240,240 }, { 255,220,220 }, { 255,160,160 }, { 255, 32, 32 },
+   { 200,120,160 }, { 200, 60,150 }, { 220,100,130 }, { 255,  0,128 },
+   { 240,240,255 }, { 220,220,255 }, { 160,160,255 }, {  32, 32,255 },
+   { 120,160,200 }, {  60,150,200 }, { 100,130,220 }, {   0,128,255 },
+   { 240,255,240 }, { 220,255,220 }, { 160,255,160 }, {  32,255, 32 },
+   { 160,200,120 }, { 150,200, 60 }, { 130,220,100 }, { 128,255,  0 },
+   { 255,255,240 }, { 255,255,220 }, { 220,220,180 }, { 255,255, 32 },
+   { 200,160,120 }, { 200,150, 60 }, { 220,130,100 }, { 255,128,  0 },
+   { 255,240,255 }, { 255,220,255 }, { 220,180,220 }, { 255, 32,255 },
+   { 160,120,200 }, { 150, 60,200 }, { 130,100,220 }, { 128,  0,255 },
+   { 240,255,255 }, { 220,255,255 }, { 180,220,220 }, {  32,255,255 },
+   { 120,200,160 }, {  60,200,150 }, { 100,220,130 }, {   0,255,128 },
+};
+
 static float stbvox_default_ambient[4][4] =
 {
    { 0,0,1      ,0 }, // reversed lighting direction
@@ -1268,7 +1377,6 @@ static float stbvox_default_ambient[4][4] =
    { 0.5,0.5,0.5,1.0f/1000.0f/1000.0f }, // fog data for simple_fog
 };
 
-static unsigned char stbvox_default_palette_compact[64][3];
 static float stbvox_default_palette[64][4];
 
 static void stbvox_build_default_palette(void)
@@ -1381,7 +1489,7 @@ static char *stbvox_fragment_program =
       #if defined(STBVOX_ICONFIG_GLSL)
          "#define rlerp(t,x,y) mix(x,y,t)\n"
       #elif defined(STBVOX_CONFIG_HLSL)
-         "#define rlerp(t,x,y) lerp(x,t,y)\n"
+         "#define rlerp(t,x,y) lerp(x,y,t)\n"
       #else
          #error "need definition of rlerp()"
       #endif
@@ -1872,1593 +1980,1442 @@ stbvox_mesh_face stbvox_compute_mesh_face_value(stbvox_mesh_maker *mm, stbvox_ro
    return face_data;
 }
 
+// these are the types of faces each block can have
+enum
+{
+   STBVOX_FT_none    ,
+   STBVOX_FT_upper   ,
+   STBVOX_FT_lower   ,
+   STBVOX_FT_solid   ,
+   STBVOX_FT_diag_012,
+   STBVOX_FT_diag_023,
+   STBVOX_FT_diag_013,
+   STBVOX_FT_diag_123,
+   STBVOX_FT_force   , // can't be covered up, used for internal faces, also hides nothing
+   STBVOX_FT_partial , // only covered by solid, never covers anything else
+
+   STBVOX_FT_count
+};
+
 static unsigned char stbvox_face_lerp[6] = { 0,2,0,2,4,4 };
 static unsigned char stbvox_vert3_lerp[5] = { 0,3,6,9,12 };
 static unsigned char stbvox_vert_lerp_for_face_lerp[4] = { 0, 4, 7, 7 };
 static unsigned char stbvox_face3_lerp[6] = { 0,3,6,9,12,14 };
 static unsigned char stbvox_vert_lerp_for_simple[4] = { 0,2,5,7 };
 static unsigned char stbvox_face3_updown[8] = { 0,2,5,7,0,2,5,7 }; // ignore top bit
+
 // vertex offsets for face vertices
-static unsigned char stbvox_vertex_vector[6][4][3];
-static stbvox_mesh_vertex stbvox_vmesh_delta_normal[6][4];
-static stbvox_mesh_vertex stbvox_vmesh_pre_vheight[6][4];
-static stbvox_mesh_vertex stbvox_vmesh_delta_half_z[6][4];
-static stbvox_mesh_vertex stbvox_vmesh_crossed_pair[6][4];
+static unsigned char stbvox_vertex_vector[6][4][3] =
+{
+   { { 1,0,1 }, { 1,1,1 }, { 1,1,0 }, { 1,0,0 } }, // east
+   { { 1,1,1 }, { 0,1,1 }, { 0,1,0 }, { 1,1,0 } }, // north
+   { { 0,1,1 }, { 0,0,1 }, { 0,0,0 }, { 0,1,0 } }, // west
+   { { 0,0,1 }, { 1,0,1 }, { 1,0,0 }, { 0,0,0 } }, // south
+   { { 0,1,1 }, { 1,1,1 }, { 1,0,1 }, { 0,0,1 } }, // up
+   { { 0,0,0 }, { 1,0,0 }, { 1,1,0 }, { 0,1,0 } }, // down
+};
 
 // stbvox_vertex_vector, but read coordinates as binary numbers, zyx
-static unsigned char stbvox_vertex_selector[6][4];
-
-void stbvox_get_quad_vertex_pointer(stbvox_mesh_maker *mm, int mesh, stbvox_mesh_vertex **vertices, stbvox_mesh_face face)
+static unsigned char stbvox_vertex_selector[6][4] =
 {
-   char *p = mm->output_cur[mesh][0];
-   int step = mm->output_step[mesh][0];
-
-   // allocate a new quad from the mesh
-   vertices[0] = (stbvox_mesh_vertex *) p; p += step;
-   vertices[1] = (stbvox_mesh_vertex *) p; p += step;
-   vertices[2] = (stbvox_mesh_vertex *) p; p += step;
-   vertices[3] = (stbvox_mesh_vertex *) p; p += step;
-   mm->output_cur[mesh][0] = p;
-
-   // output the face
-   #ifdef STBVOX_ICONFIG_FACE_ATTRIBUTE
-      // write face as interleaved vertex data
-      *(stbvox_mesh_face *) (vertices[0]+1) = face;
-      *(stbvox_mesh_face *) (vertices[1]+1) = face;
-      *(stbvox_mesh_face *) (vertices[2]+1) = face;
-      *(stbvox_mesh_face *) (vertices[3]+1) = face;
-   #else
-      *(stbvox_mesh_face *) mm->output_cur[mesh][1] = face;
-      mm->output_cur[mesh][1] += 4;
-   #endif
-}
+   { 5,7,3,1 },
+   { 7,6,2,3 },
+   { 6,4,0,2 },
+   { 4,5,1,0 },
+   { 6,7,5,4 },
+   { 0,1,3,2 },
+};
 
-void stbvox_make_mesh_for_face(stbvox_mesh_maker *mm, stbvox_rotate rot, int face, int v_off, stbvox_pos pos, stbvox_mesh_vertex vertbase, stbvox_mesh_vertex *face_coord, unsigned char mesh, int normal)
+static stbvox_mesh_vertex stbvox_vmesh_delta_normal[6][4] =
 {
-   stbvox_mesh_face face_data = stbvox_compute_mesh_face_value(mm,rot,face,v_off, normal);
+   {  stbvox_vertex_encode(1,0,1,0,0) , 
+      stbvox_vertex_encode(1,1,1,0,0) ,
+      stbvox_vertex_encode(1,1,0,0,0) ,
+      stbvox_vertex_encode(1,0,0,0,0)  },
+   {  stbvox_vertex_encode(1,1,1,0,0) ,
+      stbvox_vertex_encode(0,1,1,0,0) ,
+      stbvox_vertex_encode(0,1,0,0,0) ,
+      stbvox_vertex_encode(1,1,0,0,0)  },
+   {  stbvox_vertex_encode(0,1,1,0,0) ,
+      stbvox_vertex_encode(0,0,1,0,0) ,
+      stbvox_vertex_encode(0,0,0,0,0) ,
+      stbvox_vertex_encode(0,1,0,0,0)  },
+   {  stbvox_vertex_encode(0,0,1,0,0) ,
+      stbvox_vertex_encode(1,0,1,0,0) ,
+      stbvox_vertex_encode(1,0,0,0,0) ,
+      stbvox_vertex_encode(0,0,0,0,0)  },
+   {  stbvox_vertex_encode(0,1,1,0,0) ,
+      stbvox_vertex_encode(1,1,1,0,0) ,
+      stbvox_vertex_encode(1,0,1,0,0) ,
+      stbvox_vertex_encode(0,0,1,0,0)  },
+   {  stbvox_vertex_encode(0,0,0,0,0) ,
+      stbvox_vertex_encode(1,0,0,0,0) ,
+      stbvox_vertex_encode(1,1,0,0,0) ,
+      stbvox_vertex_encode(0,1,0,0,0)  }
+};
 
-   // still need to compute ao & texlerp for each vertex
+static stbvox_mesh_vertex stbvox_vmesh_pre_vheight[6][4] =
+{
+   {  stbvox_vertex_encode(1,0,0,0,0) , 
+      stbvox_vertex_encode(1,1,0,0,0) ,
+      stbvox_vertex_encode(1,1,0,0,0) ,
+      stbvox_vertex_encode(1,0,0,0,0)  },
+   {  stbvox_vertex_encode(1,1,0,0,0) ,
+      stbvox_vertex_encode(0,1,0,0,0) ,
+      stbvox_vertex_encode(0,1,0,0,0) ,
+      stbvox_vertex_encode(1,1,0,0,0)  },
+   {  stbvox_vertex_encode(0,1,0,0,0) ,
+      stbvox_vertex_encode(0,0,0,0,0) ,
+      stbvox_vertex_encode(0,0,0,0,0) ,
+      stbvox_vertex_encode(0,1,0,0,0)  },
+   {  stbvox_vertex_encode(0,0,0,0,0) ,
+      stbvox_vertex_encode(1,0,0,0,0) ,
+      stbvox_vertex_encode(1,0,0,0,0) ,
+      stbvox_vertex_encode(0,0,0,0,0)  },
+   {  stbvox_vertex_encode(0,1,0,0,0) ,
+      stbvox_vertex_encode(1,1,0,0,0) ,
+      stbvox_vertex_encode(1,0,0,0,0) ,
+      stbvox_vertex_encode(0,0,0,0,0)  },
+   {  stbvox_vertex_encode(0,0,0,0,0) ,
+      stbvox_vertex_encode(1,0,0,0,0) ,
+      stbvox_vertex_encode(1,1,0,0,0) ,
+      stbvox_vertex_encode(0,1,0,0,0)  }
+};
 
-   // first compute texlerp into p1
-   stbvox_mesh_vertex p1[4] = { 0 };
+static stbvox_mesh_vertex stbvox_vmesh_delta_half_z[6][4] =
+{
+   { stbvox_vertex_encode(1,0,2,0,0) , 
+     stbvox_vertex_encode(1,1,2,0,0) ,
+     stbvox_vertex_encode(1,1,0,0,0) ,
+     stbvox_vertex_encode(1,0,0,0,0)  },
+   { stbvox_vertex_encode(1,1,2,0,0) ,
+     stbvox_vertex_encode(0,1,2,0,0) ,
+     stbvox_vertex_encode(0,1,0,0,0) ,
+     stbvox_vertex_encode(1,1,0,0,0)  },
+   { stbvox_vertex_encode(0,1,2,0,0) ,
+     stbvox_vertex_encode(0,0,2,0,0) ,
+     stbvox_vertex_encode(0,0,0,0,0) ,
+     stbvox_vertex_encode(0,1,0,0,0)  },
+   { stbvox_vertex_encode(0,0,2,0,0) ,
+     stbvox_vertex_encode(1,0,2,0,0) ,
+     stbvox_vertex_encode(1,0,0,0,0) ,
+     stbvox_vertex_encode(0,0,0,0,0)  },
+   { stbvox_vertex_encode(0,1,2,0,0) ,
+     stbvox_vertex_encode(1,1,2,0,0) ,
+     stbvox_vertex_encode(1,0,2,0,0) ,
+     stbvox_vertex_encode(0,0,2,0,0)  },
+   { stbvox_vertex_encode(0,0,0,0,0) ,
+     stbvox_vertex_encode(1,0,0,0,0) ,
+     stbvox_vertex_encode(1,1,0,0,0) ,
+     stbvox_vertex_encode(0,1,0,0,0)  }
+};
 
-   if (mm->input.block_texlerp) {
-      stbvox_block_type bt = mm->input.blocktype[v_off];
-      unsigned char val = mm->input.block_texlerp[bt];
-      p1[0] = p1[1] = p1[2] = p1[3] = stbvox_vertex_encode(0,0,0,0,val);
-   } else if (mm->input.block_texlerp_face) {
-      stbvox_block_type bt = mm->input.blocktype[v_off];
-      unsigned char bt_face = STBVOX_ROTATE(face, rot.block);
-      unsigned char val = mm->input.block_texlerp_face[bt][bt_face];
-      p1[0] = p1[1] = p1[2] = p1[3] = stbvox_vertex_encode(0,0,0,0,val);
-   } else if (mm->input.texlerp_face3) {
-      unsigned char val = (mm->input.texlerp_face3[v_off] >> stbvox_face3_lerp[face]) & 7;
-      if (face >= STBVOX_FACE_up)
-         val = stbvox_face3_updown[val];
-      p1[0] = p1[1] = p1[2] = p1[3] = stbvox_vertex_encode(0,0,0,0,val);
-   } else if (mm->input.texlerp_simple) {
-      unsigned char val = mm->input.texlerp_simple[v_off];
-      unsigned char lerp_face = (val >> 2) & 7;
-      if (lerp_face == face) {
-         p1[0] = (mm->input.texlerp_simple[v_off + mm->cube_vertex_offset[face][0]] >> 5) & 7;
-         p1[1] = (mm->input.texlerp_simple[v_off + mm->cube_vertex_offset[face][1]] >> 5) & 7;
-         p1[2] = (mm->input.texlerp_simple[v_off + mm->cube_vertex_offset[face][2]] >> 5) & 7;
-         p1[3] = (mm->input.texlerp_simple[v_off + mm->cube_vertex_offset[face][3]] >> 5) & 7;
-         p1[0] = stbvox_vertex_encode(0,0,0,0,p1[0]);
-         p1[1] = stbvox_vertex_encode(0,0,0,0,p1[1]);
-         p1[2] = stbvox_vertex_encode(0,0,0,0,p1[2]);
-         p1[3] = stbvox_vertex_encode(0,0,0,0,p1[3]);
-      } else {
-         unsigned char base = stbvox_vert_lerp_for_simple[val&3];
-         p1[0] = p1[1] = p1[2] = p1[3] = stbvox_vertex_encode(0,0,0,0,base);
-      }
-   } else if (mm->input.texlerp) {
-      unsigned char facelerp = (mm->input.texlerp[v_off] >> stbvox_face_lerp[face]) & 3;
-      if (facelerp == STBVOX_TEXLERP_FACE_use_vert) {
-         if (mm->input.texlerp_vert3 && face != STBVOX_FACE_down) {
-            unsigned char shift = stbvox_vert3_lerp[face];
-            p1[0] = (mm->input.texlerp_vert3[mm->cube_vertex_offset[face][0]] >> shift) & 7;
-            p1[1] = (mm->input.texlerp_vert3[mm->cube_vertex_offset[face][1]] >> shift) & 7;
-            p1[2] = (mm->input.texlerp_vert3[mm->cube_vertex_offset[face][2]] >> shift) & 7;
-            p1[3] = (mm->input.texlerp_vert3[mm->cube_vertex_offset[face][3]] >> shift) & 7;
-         } else {
-            p1[0] = stbvox_vert_lerp_for_simple[mm->input.texlerp[mm->cube_vertex_offset[face][0]]>>6];
-            p1[1] = stbvox_vert_lerp_for_simple[mm->input.texlerp[mm->cube_vertex_offset[face][1]]>>6];
-            p1[2] = stbvox_vert_lerp_for_simple[mm->input.texlerp[mm->cube_vertex_offset[face][2]]>>6];
-            p1[3] = stbvox_vert_lerp_for_simple[mm->input.texlerp[mm->cube_vertex_offset[face][3]]>>6];
-         }
-         p1[0] = stbvox_vertex_encode(0,0,0,0,p1[0]);
-         p1[1] = stbvox_vertex_encode(0,0,0,0,p1[1]);
-         p1[2] = stbvox_vertex_encode(0,0,0,0,p1[2]);
-         p1[3] = stbvox_vertex_encode(0,0,0,0,p1[3]);
-      } else {
-         p1[0] = p1[1] = p1[2] = p1[3] = stbvox_vertex_encode(0,0,0,0,stbvox_vert_lerp_for_face_lerp[facelerp]);
-      }
-   } else {
-      p1[0] = p1[1] = p1[2] = p1[3] = stbvox_vertex_encode(0,0,0,0,7);
-   }
+static stbvox_mesh_vertex stbvox_vmesh_crossed_pair[6][4] =
+{
+   { stbvox_vertex_encode(1,0,2,0,0) , 
+     stbvox_vertex_encode(0,1,2,0,0) ,
+     stbvox_vertex_encode(0,1,0,0,0) ,
+     stbvox_vertex_encode(1,0,0,0,0)  },
+   { stbvox_vertex_encode(1,1,2,0,0) ,
+     stbvox_vertex_encode(0,0,2,0,0) ,
+     stbvox_vertex_encode(0,0,0,0,0) ,
+     stbvox_vertex_encode(1,1,0,0,0)  },
+   { stbvox_vertex_encode(0,1,2,0,0) ,
+     stbvox_vertex_encode(1,0,2,0,0) ,
+     stbvox_vertex_encode(1,0,0,0,0) ,
+     stbvox_vertex_encode(0,1,0,0,0)  },
+   { stbvox_vertex_encode(0,0,2,0,0) ,
+     stbvox_vertex_encode(1,1,2,0,0) ,
+     stbvox_vertex_encode(1,1,0,0,0) ,
+     stbvox_vertex_encode(0,0,0,0,0)  },
+   // not used, so we leave it non-degenerate to make sure it doesn't get gen'd accidentally
+   { stbvox_vertex_encode(0,1,2,0,0) ,
+     stbvox_vertex_encode(1,1,2,0,0) ,
+     stbvox_vertex_encode(1,0,2,0,0) ,
+     stbvox_vertex_encode(0,0,2,0,0)  },
+   { stbvox_vertex_encode(0,0,0,0,0) ,
+     stbvox_vertex_encode(1,0,0,0,0) ,
+     stbvox_vertex_encode(1,1,0,0,0) ,
+     stbvox_vertex_encode(0,1,0,0,0)  }
+};
 
-   {
-      stbvox_mesh_vertex *mv[4];
-      stbvox_get_quad_vertex_pointer(mm, mesh, mv, face_data);
+#define STBVOX_MAX_GEOM     16
+#define STBVOX_NUM_ROTATION  4
 
-      if (mm->input.lighting) {
-         // @TODO: lighting at block centers, but not gathered, instead constant-per-face
-         if (mm->input.lighting_at_vertices) {
-            int i;
-            for (i=0; i < 4; ++i) {
-               *mv[i] = vertbase + face_coord[i]
-                          + stbvox_vertex_encode(0,0,0,mm->input.lighting[v_off + mm->cube_vertex_offset[face][i]] & 63,0)
-                          + p1[i];
-            }
-         } else {
-            unsigned char *amb = &mm->input.lighting[v_off];
-            int i,j;
-            #ifdef STBVOX_ROTATION_IN_LIGHTING
-            #define STBVOX_GET_LIGHTING(light) ((light) & ~3)
-            #define STBVOX_LIGHTING_ROUNDOFF   8
-            #else
-            #define STBVOX_GET_LIGHTING(light) (light)
-            #define STBVOX_LIGHTING_ROUNDOFF   2
-            #endif
+// this is used to determine if a face is ever generated at all
+static unsigned char stbvox_hasface[STBVOX_MAX_GEOM][STBVOX_NUM_ROTATION] =
+{
+   { 0,0,0,0 }, // empty
+   { 0,0,0,0 }, // knockout
+   { 63,63,63,63 }, // solid
+   { 63,63,63,63 }, // transp
+   { 63,63,63,63 }, // slab
+   { 63,63,63,63 }, // slab
+   { 1|2|4|48, 8|1|2|48, 4|8|1|48, 2|4|8|48, }, // floor slopes
+   { 1|2|4|48, 8|1|2|48, 4|8|1|48, 2|4|8|48, }, // ceil slopes
+   { 47,47,47,47 }, // wall-projected diagonal with down face
+   { 31,31,31,31 }, // wall-projected diagonal with up face
+   { 63,63,63,63 }, // crossed-pair has special handling, but avoid early-out
+   { 63,63,63,63 }, // force
+   { 63,63,63,63 },
+   { 63,63,63,63 },
+   { 63,63,63,63 },
+   { 63,63,63,63 },
+};
 
-            for (i=0; i < 4; ++i) {
-               // for each vertex, gather from the four neighbor blocks it's facing
-               unsigned char *vamb = &amb[mm->cube_vertex_offset[face][i]];
-               int total=0;
-               for (j=0; j < 4; ++j)
-                  total += STBVOX_GET_LIGHTING(vamb[mm->vertex_gather_offset[face][j]]);
-               *mv[i] = vertbase + face_coord[i]
-                          + stbvox_vertex_encode(0,0,0,(total+STBVOX_LIGHTING_ROUNDOFF)>>4,0)
-                          + p1[i];
-                          // >> 4 is because:
-                          //   >> 2 to divide by 4 to get average over 4 samples
-                          //   >> 2 because input is 8 bits, output is 6 bits
-            }
+// this determines which face type above is visible on each side of the geometry
+static unsigned char stbvox_facetype[STBVOX_GEOM_count][6] =
+{
+   { 0, },  // STBVOX_GEOM_empty
+   { STBVOX_FT_solid, STBVOX_FT_solid, STBVOX_FT_solid, STBVOX_FT_solid, STBVOX_FT_solid, STBVOX_FT_solid }, // knockout
+   { STBVOX_FT_solid, STBVOX_FT_solid, STBVOX_FT_solid, STBVOX_FT_solid, STBVOX_FT_solid, STBVOX_FT_solid }, // solid
+   { STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force }, // transp
 
-            // @TODO: note that gathering baked *lighting*
-            // is different from gathering baked ao; baked ao can count
-            // solid blocks as 0 ao, but baked lighting wants average
-            // of non-blocked--not take average & treat blocked as 0. And
-            // we can't bake the right value into the solid blocks
-            // because they can have different lighting values on
-            // different sides. So we need to actually gather and
-            // then divide by 0..4 (which we can do with a table-driven
-            // multiply, or have an 'if' for the 3 case)
+   { STBVOX_FT_upper, STBVOX_FT_upper, STBVOX_FT_upper, STBVOX_FT_upper, STBVOX_FT_solid, STBVOX_FT_force },
+   { STBVOX_FT_lower, STBVOX_FT_lower, STBVOX_FT_lower, STBVOX_FT_lower, STBVOX_FT_force, STBVOX_FT_solid },
+   { STBVOX_FT_diag_123, STBVOX_FT_solid, STBVOX_FT_diag_023, STBVOX_FT_none, STBVOX_FT_force, STBVOX_FT_solid },
+   { STBVOX_FT_diag_012, STBVOX_FT_solid, STBVOX_FT_diag_013, STBVOX_FT_none, STBVOX_FT_solid, STBVOX_FT_force },
 
-         }
-      } else {
-         vertbase += stbvox_vertex_encode(0,0,0,63,0);
-         *mv[0] = vertbase + face_coord[0] + p1[0];
-         *mv[1] = vertbase + face_coord[1] + p1[1];
-         *mv[2] = vertbase + face_coord[2] + p1[2];
-         *mv[3] = vertbase + face_coord[3] + p1[3];
-      }
-   }
-}
+   { STBVOX_FT_diag_123, STBVOX_FT_solid, STBVOX_FT_diag_023, STBVOX_FT_force, STBVOX_FT_none, STBVOX_FT_solid },
+   { STBVOX_FT_diag_012, STBVOX_FT_solid, STBVOX_FT_diag_013, STBVOX_FT_force, STBVOX_FT_solid, STBVOX_FT_none },
+   { STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force, 0,0 }, // crossed pair
+   { STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force }, // GEOM_force
 
-#ifndef STBVOX_OPTIMIZED_VHEIGHT
-static stbvox_face_up_normal_012[4][4][4];
-static stbvox_face_up_normal_013[4][4][4];
-static stbvox_face_up_normal_023[4][4][4];
-static stbvox_face_up_normal_123[4][4][4];
+   { STBVOX_FT_partial,STBVOX_FT_partial,STBVOX_FT_partial,STBVOX_FT_partial, STBVOX_FT_force, STBVOX_FT_solid }, // floor vheight, all neighbors forced
+   { STBVOX_FT_partial,STBVOX_FT_partial,STBVOX_FT_partial,STBVOX_FT_partial, STBVOX_FT_force, STBVOX_FT_solid }, // floor vheight, all neighbors forced
+   { STBVOX_FT_partial,STBVOX_FT_partial,STBVOX_FT_partial,STBVOX_FT_partial, STBVOX_FT_solid, STBVOX_FT_force }, // ceil vheight, all neighbors forced
+   { STBVOX_FT_partial,STBVOX_FT_partial,STBVOX_FT_partial,STBVOX_FT_partial, STBVOX_FT_solid, STBVOX_FT_force }, // ceil vheight, all neighbors forced
+};
 
-// render non-planar quads by splitting into two triangles, rendering each as a degenerate quad
-static void stbvox_make_12_split_mesh_for_face(stbvox_mesh_maker *mm, stbvox_rotate rot, int face, int v_off, stbvox_pos pos, stbvox_mesh_vertex vertbase, stbvox_mesh_vertex *face_coord, unsigned char mesh, unsigned char *ht)
+// This table indicates what normal to use for the "up" face of a sloped geom
+// @TODO this could be done with math given the current arrangement of the enum, but let's not require it
+static unsigned char stbvox_floor_slope_for_rot[4] =
 {
-   stbvox_mesh_vertex v[4];
-
-   unsigned char normal1 = stbvox_face_up_normal_012[ht[2]][ht[1]][ht[0]];
-   unsigned char normal2 = stbvox_face_up_normal_123[ht[3]][ht[2]][ht[1]];
+   STBVF_su,
+   STBVF_wu, // @TODO: why is this reversed from what it should be? this is a north-is-up face, so slope should be south&up
+   STBVF_nu,
+   STBVF_eu,
+};
 
-   if (face == STBVOX_FACE_down) {
-      normal1 = stbvox_reverse_face[normal1];
-      normal2 = stbvox_reverse_face[normal2];
-   }
+static unsigned char stbvox_ceil_slope_for_rot[4] =
+{
+   STBVF_sd,
+   STBVF_ed,
+   STBVF_nd,
+   STBVF_wd,
+};
 
-   // the floor side face_coord is stored in order NW,NE,SE,SW, but ht[] is stored SW,SE,NW,NE
-   v[0] = face_coord[2];
-   v[1] = face_coord[3];
-   v[2] = face_coord[0];
-   v[3] = face_coord[2];
-   stbvox_make_mesh_for_face(mm, rot, face, v_off, pos, vertbase, v, mesh, normal1);
-   v[1] = face_coord[0];
-   v[2] = face_coord[1];
-   stbvox_make_mesh_for_face(mm, rot, face, v_off, pos, vertbase, v, mesh, normal2);
-}
+// this table indicates whether, for each pair of types above, a face is visible.
+// each value indicates whether a given type is visible for all neighbor types
+static unsigned short stbvox_face_visible[STBVOX_FT_count] =
+{
+   // we encode the table by listing which cases cause *obscuration*, and bitwise inverting that
+   // table is pre-shifted by 5 to save a shift when it's accessed
+   (unsigned short) ((~0x07ff                                          )<<5),  // none is completely obscured by everything
+   (unsigned short) ((~((1<<STBVOX_FT_solid) | (1<<STBVOX_FT_upper)   ))<<5),  // upper
+   (unsigned short) ((~((1<<STBVOX_FT_solid) | (1<<STBVOX_FT_lower)   ))<<5),  // lower
+   (unsigned short) ((~((1<<STBVOX_FT_solid)                          ))<<5),  // solid is only completely obscured only by solid
+   (unsigned short) ((~((1<<STBVOX_FT_solid) | (1<<STBVOX_FT_diag_013)))<<5),  // diag012 matches diag013
+   (unsigned short) ((~((1<<STBVOX_FT_solid) | (1<<STBVOX_FT_diag_123)))<<5),  // diag023 matches diag123
+   (unsigned short) ((~((1<<STBVOX_FT_solid) | (1<<STBVOX_FT_diag_012)))<<5),  // diag013 matches diag012
+   (unsigned short) ((~((1<<STBVOX_FT_solid) | (1<<STBVOX_FT_diag_023)))<<5),  // diag123 matches diag023
+   (unsigned short) ((~0                                               )<<5),  // force is always rendered regardless, always forces neighbor
+   (unsigned short) ((~((1<<STBVOX_FT_solid)                          ))<<5),  // partial is only completely obscured only by solid
+};
 
-static void stbvox_make_03_split_mesh_for_face(stbvox_mesh_maker *mm, stbvox_rotate rot, int face, int v_off, stbvox_pos pos, stbvox_mesh_vertex vertbase, stbvox_mesh_vertex *face_coord, unsigned char mesh, unsigned char *ht)
+// the vertex heights of the block types, in binary vertex order (zyx):
+// lower: SW, SE, NW, NE; upper: SW, SE, NW, NE
+static stbvox_mesh_vertex stbvox_geometry_vheight[8][8] =
 {
-   stbvox_mesh_vertex v[4];
+   #define STBVOX_HEIGHTS(a,b,c,d,e,f,g,h) \
+     { stbvox_vertex_encode(0,0,a,0,0),  \
+       stbvox_vertex_encode(0,0,b,0,0),  \
+       stbvox_vertex_encode(0,0,c,0,0),  \
+       stbvox_vertex_encode(0,0,d,0,0),  \
+       stbvox_vertex_encode(0,0,e,0,0),  \
+       stbvox_vertex_encode(0,0,f,0,0),  \
+       stbvox_vertex_encode(0,0,g,0,0),  \
+       stbvox_vertex_encode(0,0,h,0,0) }
 
-   unsigned char normal1 = stbvox_face_up_normal_013[ht[3]][ht[1]][ht[0]];
-   unsigned char normal2 = stbvox_face_up_normal_023[ht[3]][ht[2]][ht[0]];
+   STBVOX_HEIGHTS(0,0,0,0, 2,2,2,2),
+   STBVOX_HEIGHTS(0,0,0,0, 2,2,2,2),
+   STBVOX_HEIGHTS(0,0,0,0, 2,2,2,2),
+   STBVOX_HEIGHTS(0,0,0,0, 2,2,2,2),
+   STBVOX_HEIGHTS(1,1,1,1, 2,2,2,2),
+   STBVOX_HEIGHTS(0,0,0,0, 1,1,1,1),
+   STBVOX_HEIGHTS(0,0,0,0, 0,0,2,2),
+   STBVOX_HEIGHTS(2,2,0,0, 2,2,2,2),
+};
 
-   if (face == STBVOX_FACE_down) {
-      normal1 = stbvox_reverse_face[normal1];
-      normal2 = stbvox_reverse_face[normal2];
-   }
-
-   v[0] = face_coord[1];
-   v[1] = face_coord[2];
-   v[2] = face_coord[3];
-   v[3] = face_coord[1];
-   stbvox_make_mesh_for_face(mm, rot, face, v_off, pos, vertbase, v, mesh, normal1);
-   v[1] = face_coord[3];
-   v[2] = face_coord[0];
-   stbvox_make_mesh_for_face(mm, rot, face, v_off, pos, vertbase, v, mesh, normal2);  // this one is correct!
-}
-#endif
-
-#ifndef STBVOX_CONFIG_PRECISION_Z
-#define STBVOX_CONFIG_PRECISION_Z 1
-#endif
-
-// simple case for mesh generation: we have only solid and empty blocks
-static void stbvox_make_mesh_for_block(stbvox_mesh_maker *mm, stbvox_pos pos, int v_off, stbvox_mesh_vertex *vmesh)
+// rotate vertices defined as [z][y][x] coords
+static unsigned char stbvox_rotate_vertex[8][4] =
 {
-   int ns_off = mm->y_stride_in_bytes;
-   int ew_off = mm->x_stride_in_bytes;
-
-   unsigned char *blockptr = &mm->input.blocktype[v_off];
-   stbvox_mesh_vertex basevert = stbvox_vertex_encode(pos.x, pos.y, pos.z << STBVOX_CONFIG_PRECISION_Z , 0,0);
-
-   stbvox_rotate rot = { 0,0,0,0 };
-   unsigned char simple_rot = 0;
-
-   unsigned char mesh = mm->default_mesh;
-
-   if (mm->input.selector)
-      mesh = mm->input.selector[v_off];
+   { 0,1,3,2 }, // zyx=000
+   { 1,3,2,0 }, // zyx=001
+   { 2,0,1,3 }, // zyx=010
+   { 3,2,0,1 }, // zyx=011
+   { 4,5,7,6 }, // zyx=100
+   { 5,7,6,4 }, // zyx=101
+   { 6,4,5,7 }, // zyx=110
+   { 7,6,4,5 }, // zyx=111
+};
 
-   // check if we're going off the end
-   if (mm->output_cur[mesh][0] + mm->output_size[mesh][0]*6 > mm->output_end[mesh][0]) {
-      mm->full = 1;
-      return;
+#ifdef STBVOX_OPTIMIZED_VHEIGHT
+// optimized vheight generates a single normal over the entire face, even if it's not planar
+static stbvox_optimized_face_up_normal[4][4][4][4] =
+{
+   {
+      {
+         { STBVF_u   , STBVF_ne_u, STBVF_ne_u, STBVF_ne_u, },
+         { STBVF_nw_u, STBVF_nu  , STBVF_nu  , STBVF_ne_u, },
+         { STBVF_nw_u, STBVF_nu  , STBVF_nu  , STBVF_nu  , },
+         { STBVF_nw_u, STBVF_nw_u, STBVF_nu  , STBVF_nu  , },
+      },{
+         { STBVF_su  , STBVF_eu  , STBVF_eu  , STBVF_ne_u, },
+         { STBVF_u   , STBVF_ne_u, STBVF_ne_u, STBVF_ne_u, },
+         { STBVF_nw_u, STBVF_nu  , STBVF_nu  , STBVF_ne_u, },
+         { STBVF_nw_u, STBVF_nu  , STBVF_nu  , STBVF_nu  , },
+      },{
+         { STBVF_eu  , STBVF_eu  , STBVF_eu  , STBVF_eu  , },
+         { STBVF_su  , STBVF_eu  , STBVF_eu  , STBVF_ne_u, },
+         { STBVF_u   , STBVF_ne_u, STBVF_ne_u, STBVF_ne_u, },
+         { STBVF_nw_u, STBVF_nu  , STBVF_nu  , STBVF_ne_u, },
+      },{
+         { STBVF_eu  , STBVF_eu  , STBVF_eu  , STBVF_eu  , },
+         { STBVF_eu  , STBVF_eu  , STBVF_eu  , STBVF_eu  , },
+         { STBVF_su  , STBVF_eu  , STBVF_eu  , STBVF_ne_u, },
+         { STBVF_u   , STBVF_ne_u, STBVF_ne_u, STBVF_ne_u, },
+      },
+   },{
+      {
+         { STBVF_sw_u, STBVF_u   , STBVF_ne_u, STBVF_ne_u, },
+         { STBVF_wu  , STBVF_nw_u, STBVF_nu  , STBVF_nu  , },
+         { STBVF_wu  , STBVF_nw_u, STBVF_nu  , STBVF_nu  , },
+         { STBVF_nw_u, STBVF_nw_u, STBVF_nw_u, STBVF_nu  , },
+      },{
+         { STBVF_su  , STBVF_su  , STBVF_eu  , STBVF_eu  , },
+         { STBVF_sw_u, STBVF_u   , STBVF_ne_u, STBVF_ne_u, },
+         { STBVF_wu  , STBVF_nw_u, STBVF_nu  , STBVF_nu  , },
+         { STBVF_wu  , STBVF_nw_u, STBVF_nu  , STBVF_nu  , },
+      },{
+         { STBVF_su  , STBVF_eu  , STBVF_eu  , STBVF_eu  , },
+         { STBVF_su  , STBVF_su  , STBVF_eu  , STBVF_eu  , },
+         { STBVF_sw_u, STBVF_u   , STBVF_ne_u, STBVF_ne_u, },
+         { STBVF_wu  , STBVF_nw_u, STBVF_nu  , STBVF_nu  , },
+      },{
+         { STBVF_su  , STBVF_eu  , STBVF_eu  , STBVF_eu  , },
+         { STBVF_su  , STBVF_eu  , STBVF_eu  , STBVF_eu  , },
+         { STBVF_su  , STBVF_su  , STBVF_eu  , STBVF_eu  , },
+         { STBVF_sw_u, STBVF_u   , STBVF_ne_u, STBVF_ne_u, },
+      },
+   },{
+      {
+         { STBVF_sw_u, STBVF_sw_u, STBVF_u   , STBVF_ne_u, },
+         { STBVF_wu  , STBVF_wu  , STBVF_nw_u, STBVF_nu  , },
+         { STBVF_wu  , STBVF_wu  , STBVF_nw_u, STBVF_nu  , },
+         { STBVF_wu  , STBVF_nw_u, STBVF_nw_u, STBVF_nw_u, },
+      },{
+         { STBVF_su  , STBVF_su  , STBVF_su  , STBVF_eu  , },
+         { STBVF_sw_u, STBVF_sw_u, STBVF_u   , STBVF_ne_u, },
+         { STBVF_wu  , STBVF_wu  , STBVF_nw_u, STBVF_nu  , },
+         { STBVF_wu  , STBVF_wu  , STBVF_nw_u, STBVF_nu  , },
+      },{
+         { STBVF_su  , STBVF_su  , STBVF_eu  , STBVF_eu  , },
+         { STBVF_su  , STBVF_su  , STBVF_su  , STBVF_eu  , },
+         { STBVF_sw_u, STBVF_sw_u, STBVF_u   , STBVF_ne_u, },
+         { STBVF_wu  , STBVF_wu  , STBVF_nw_u, STBVF_nu  , },
+      },{
+         { STBVF_su  , STBVF_su  , STBVF_eu  , STBVF_eu  , },
+         { STBVF_su  , STBVF_su  , STBVF_eu  , STBVF_eu  , },
+         { STBVF_su  , STBVF_su  , STBVF_su  , STBVF_eu  , },
+         { STBVF_sw_u, STBVF_sw_u, STBVF_u   , STBVF_ne_u, },
+      },
+   },{
+      {
+         { STBVF_sw_u, STBVF_sw_u, STBVF_sw_u, STBVF_u   , },
+         { STBVF_sw_u, STBVF_wu  , STBVF_wu  , STBVF_nw_u, },
+         { STBVF_wu  , STBVF_wu  , STBVF_wu  , STBVF_nw_u, },
+         { STBVF_wu  , STBVF_wu  , STBVF_nw_u, STBVF_nw_u, },
+      },{
+         { STBVF_sw_u, STBVF_su  , STBVF_su  , STBVF_su  , },
+         { STBVF_sw_u, STBVF_sw_u, STBVF_sw_u, STBVF_u   , },
+         { STBVF_sw_u, STBVF_wu  , STBVF_wu  , STBVF_nw_u, },
+         { STBVF_wu  , STBVF_wu  , STBVF_wu  , STBVF_nw_u, },
+      },{
+         { STBVF_su  , STBVF_su  , STBVF_su  , STBVF_eu  , },
+         { STBVF_sw_u, STBVF_su  , STBVF_su  , STBVF_su  , },
+         { STBVF_sw_u, STBVF_sw_u, STBVF_sw_u, STBVF_u   , },
+         { STBVF_sw_u, STBVF_wu  , STBVF_wu  , STBVF_nw_u, },
+      },{
+         { STBVF_su  , STBVF_su  , STBVF_su  , STBVF_eu  , },
+         { STBVF_su  , STBVF_su  , STBVF_su  , STBVF_eu  , },
+         { STBVF_sw_u, STBVF_su  , STBVF_su  , STBVF_su  , },
+         { STBVF_sw_u, STBVF_sw_u, STBVF_sw_u, STBVF_u   , },
+      },
+   },
+};
+#else
+// which normal to use for a given vheight that's planar
+// @TODO: this table was constructed by hand and may have bugs
+//                                 nw se sw
+static stbvox_planar_face_up_normal[4][4][4] =
+{   
+   {                                                      // sw,se,nw,ne;  ne = se+nw-sw
+      { STBVF_u   , 0         , 0         , 0          }, //  0,0,0,0; 1,0,0,-1; 2,0,0,-2; 3,0,0,-3;
+      { STBVF_u   , STBVF_u   , 0         , 0          }, //  0,1,0,1; 1,1,0, 0; 2,1,0,-1; 3,1,0,-2;
+      { STBVF_wu  , STBVF_nw_u, STBVF_nu  , 0          }, //  0,2,0,2; 1,2,0, 1; 2,2,0, 0; 3,2,0,-1;
+      { STBVF_wu  , STBVF_nw_u, STBVF_nw_u, STBVF_nu   }, //  0,3,0,3; 1,3,0, 2; 2,3,0, 1; 3,3,0, 0;
+   },{
+      { STBVF_u   , STBVF_u   , 0         , 0          }, //  0,0,1,1; 1,0,1, 0; 2,0,1,-1; 3,0,1,-2;
+      { STBVF_sw_u, STBVF_u   , STBVF_ne_u, 0          }, //  0,1,1,2; 1,1,1, 1; 2,1,1, 0; 3,1,1,-1;
+      { STBVF_sw_u, STBVF_u   , STBVF_u   , STBVF_ne_u }, //  0,2,1,3; 1,2,1, 2; 2,2,1, 1; 3,2,1, 0;
+      { 0         , STBVF_wu  , STBVF_nw_u, STBVF_nu   }, //  0,3,1,4; 1,3,1, 3; 2,3,1, 2; 3,3,1, 1;
+   },{
+      { STBVF_su  , STBVF_se_u, STBVF_eu  , 0          }, //  0,0,2,2; 1,0,2, 1; 2,0,2, 0; 3,0,2,-1;
+      { STBVF_sw_u, STBVF_u   , STBVF_u   , STBVF_ne_u }, //  0,1,2,3; 1,1,2, 2; 2,1,2, 1; 3,1,2, 0;
+      { 0         , STBVF_sw_u, STBVF_u   , STBVF_ne_u }, //  0,2,2,4; 1,2,2, 3; 2,2,2, 2; 3,2,2, 1;
+      { 0         , 0         , STBVF_u   , STBVF_u    }, //  0,3,2,5; 1,3,2, 4; 2,3,2, 3; 3,3,2, 2;
+   },{
+      { STBVF_su  , STBVF_se_u, STBVF_se_u, STBVF_eu   }, //  0,0,3,3; 1,0,3, 2; 2,0,3, 1; 3,0,3, 0;
+      { 0         , STBVF_su  , STBVF_se_u, STBVF_eu   }, //  0,1,3,4; 1,1,3, 3; 2,1,3, 2; 3,1,3, 1;
+      { 0         , 0         , STBVF_u   , STBVF_u    }, //  0,2,3,5; 1,2,3, 4; 2,2,3, 3; 3,2,3, 2;
+      { 0         , 0         , 0         , STBVF_u    }, //  0,3,3,6; 1,3,3, 5; 2,3,3, 4; 3,3,3, 3;
    }
+};
 
-   #ifdef STBVOX_ROTATION_IN_LIGHTING
-   simple_rot = mm->input.lighting[v_off] & 3;
-   #endif
-
-   if (blockptr[ 1]==0) {
-      rot.facerot = simple_rot;
-      stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_up  , v_off, pos, basevert, vmesh+4*STBVOX_FACE_up, mesh, STBVOX_FACE_up);
-   }
-   if (blockptr[-1]==0) {
-      rot.facerot = (-simple_rot) & 3;
-      stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_down, v_off, pos, basevert, vmesh+4*STBVOX_FACE_down, mesh, STBVOX_FACE_down);
+// these tables were constructed automatically using a variant of the code
+// below; however, they seem wrong, so who knows
+static stbvox_face_up_normal_012[4][4][4] =
+{
+   {
+      { STBVF_u   , STBVF_ne_u, STBVF_ne_u, STBVF_ne_u, },
+      { STBVF_wu  , STBVF_nu  , STBVF_ne_u, STBVF_ne_u, },
+      { STBVF_wu  , STBVF_nw_u, STBVF_nu  , STBVF_ne_u, },
+      { STBVF_wu  , STBVF_nw_u, STBVF_nw_u, STBVF_nu  , },
+   },{
+      { STBVF_su  , STBVF_eu  , STBVF_ne_u, STBVF_ne_u, },
+      { STBVF_sw_u, STBVF_u   , STBVF_ne_u, STBVF_ne_u, },
+      { STBVF_sw_u, STBVF_wu  , STBVF_nu  , STBVF_ne_u, },
+      { STBVF_sw_u, STBVF_wu  , STBVF_nw_u, STBVF_nu  , },
+   },{
+      { STBVF_su  , STBVF_eu  , STBVF_eu  , STBVF_ne_u, },
+      { STBVF_sw_u, STBVF_su  , STBVF_eu  , STBVF_ne_u, },
+      { STBVF_sw_u, STBVF_sw_u, STBVF_u   , STBVF_ne_u, },
+      { STBVF_sw_u, STBVF_sw_u, STBVF_wu  , STBVF_nu  , },
+   },{
+      { STBVF_su  , STBVF_su  , STBVF_eu  , STBVF_eu  , },
+      { STBVF_sw_u, STBVF_su  , STBVF_eu  , STBVF_eu  , },
+      { STBVF_sw_u, STBVF_sw_u, STBVF_su  , STBVF_eu  , },
+      { STBVF_sw_u, STBVF_sw_u, STBVF_sw_u, STBVF_u   , },
    }
+};
 
-   if (mm->input.rotate) {
-      unsigned char val = mm->input.rotate[v_off];
-      rot.block   = (val >> 0) & 3;
-      rot.overlay = (val >> 2) & 3;
-      //rot.tex2    = (val >> 4) & 3;
-      rot.ecolor  = (val >> 6) & 3;
-   } else {
-      rot.block = rot.overlay = rot.ecolor = simple_rot;
+static stbvox_face_up_normal_013[4][4][4] =
+{
+   {
+      { STBVF_u   , STBVF_eu  , STBVF_eu  , STBVF_eu  , },
+      { STBVF_nw_u, STBVF_nu  , STBVF_ne_u, STBVF_ne_u, },
+      { STBVF_nw_u, STBVF_nw_u, STBVF_nu  , STBVF_ne_u, },
+      { STBVF_nw_u, STBVF_nw_u, STBVF_nw_u, STBVF_nu  , },
+   },{
+      { STBVF_su  , STBVF_eu  , STBVF_eu  , STBVF_eu  , },
+      { STBVF_wu  , STBVF_u   , STBVF_eu  , STBVF_eu  , },
+      { STBVF_nw_u, STBVF_nw_u, STBVF_nu  , STBVF_ne_u, },
+      { STBVF_nw_u, STBVF_nw_u, STBVF_nw_u, STBVF_nu  , },
+   },{
+      { STBVF_su  , STBVF_su  , STBVF_su  , STBVF_eu  , },
+      { STBVF_sw_u, STBVF_su  , STBVF_eu  , STBVF_eu  , },
+      { STBVF_wu  , STBVF_wu  , STBVF_u   , STBVF_eu  , },
+      { STBVF_nw_u, STBVF_nw_u, STBVF_nw_u, STBVF_nu  , },
+   },{
+      { STBVF_su  , STBVF_su  , STBVF_su  , STBVF_eu  , },
+      { STBVF_sw_u, STBVF_su  , STBVF_su  , STBVF_su  , },
+      { STBVF_sw_u, STBVF_sw_u, STBVF_su  , STBVF_eu  , },
+      { STBVF_wu  , STBVF_wu  , STBVF_wu  , STBVF_u   , },
    }
-   rot.facerot = 0;
-
-   if (blockptr[ ns_off]==0)
-      stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_north, v_off, pos, basevert, vmesh+4*STBVOX_FACE_north, mesh, STBVOX_FACE_north);
-   if (blockptr[-ns_off]==0)
-      stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_south, v_off, pos, basevert, vmesh+4*STBVOX_FACE_south, mesh, STBVOX_FACE_south);
-   if (blockptr[ ew_off]==0)
-      stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_east , v_off, pos, basevert, vmesh+4*STBVOX_FACE_east, mesh, STBVOX_FACE_east);
-   if (blockptr[-ew_off]==0)
-      stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_west , v_off, pos, basevert, vmesh+4*STBVOX_FACE_west, mesh, STBVOX_FACE_west);
-}
-
+};
 
-// void stbvox_make_mesh_for_block_with_geo(stbvox_mesh_maker *mm, stbvox_pos pos, int v_off)
-//
-// complex case for mesh generation: we have lots of different
-// block types, and we don't want to generate faces of blocks
-// if they're hidden by neighbors.
-//
-// we use lots of tables to determine this: we have a table
-// which tells us what face type is generated for each type of
-// geometry, and then a table that tells us whether that type
-// is hidden by a neighbor.
-
-
-#define STBVOX_MAX_GEOM     16
-#define STBVOX_NUM_ROTATION  4
-
-// this is used to determine if a face is ever generated at all
-static unsigned char stbvox_hasface[STBVOX_MAX_GEOM][STBVOX_NUM_ROTATION];
-
-// this determines which face type above is visible on each side of the geometry
-static unsigned char stbvox_facetype[STBVOX_GEOM_count][6];
-
-// This table indicates what normal to use for the "up" face of a sloped geom
-static unsigned char stbvox_floor_slope_for_rot[4];
-static unsigned char stbvox_ceil_slope_for_rot[4];
-
-// these are the types of faces each block can have
-enum
+static stbvox_face_up_normal_023[4][4][4] =
 {
-   STBVOX_FT_none    ,
-   STBVOX_FT_upper   ,
-   STBVOX_FT_lower   ,
-   STBVOX_FT_solid   ,
-   STBVOX_FT_diag_012,
-   STBVOX_FT_diag_023,
-   STBVOX_FT_diag_013,
-   STBVOX_FT_diag_123,
-   STBVOX_FT_force   , // can't be covered up, used for internal faces, also hides nothing
-   STBVOX_FT_partial , // only covered by solid, never covers anything else
-
-   STBVOX_FT_count
+   {
+      { STBVF_u   , STBVF_nu  , STBVF_nu  , STBVF_nu  , },
+      { STBVF_eu  , STBVF_eu  , STBVF_ne_u, STBVF_ne_u, },
+      { STBVF_su  , STBVF_eu  , STBVF_eu  , STBVF_ne_u, },
+      { STBVF_eu  , STBVF_eu  , STBVF_eu  , STBVF_eu  , },
+   },{
+      { STBVF_wu  , STBVF_nw_u, STBVF_nw_u, STBVF_nw_u, },
+      { STBVF_su  , STBVF_u   , STBVF_nu  , STBVF_nu  , },
+      { STBVF_su  , STBVF_eu  , STBVF_eu  , STBVF_ne_u, },
+      { STBVF_su  , STBVF_su  , STBVF_eu  , STBVF_eu  , },
+   },{
+      { STBVF_wu  , STBVF_nw_u, STBVF_nw_u, STBVF_nw_u, },
+      { STBVF_sw_u, STBVF_wu  , STBVF_nw_u, STBVF_nw_u, },
+      { STBVF_su  , STBVF_su  , STBVF_u   , STBVF_nu  , },
+      { STBVF_su  , STBVF_su  , STBVF_eu  , STBVF_eu  , },
+   },{
+      { STBVF_wu  , STBVF_nw_u, STBVF_nw_u, STBVF_nw_u, },
+      { STBVF_sw_u, STBVF_wu  , STBVF_nw_u, STBVF_nw_u, },
+      { STBVF_sw_u, STBVF_sw_u, STBVF_wu  , STBVF_nw_u, },
+      { STBVF_su  , STBVF_su  , STBVF_su  , STBVF_u   , },
+   }
 };
 
-// this table indicates whether, for each pair of types above, a face is visible.
-// each value indicates whether a given type is visible for each neighbor type
-static unsigned short stbvox_face_visible[STBVOX_FT_count];
+static stbvox_face_up_normal_123[4][4][4] =
+{
+   {
+      { STBVF_u   , STBVF_nu  , STBVF_nu  , STBVF_nu  , },
+      { STBVF_eu  , STBVF_ne_u, STBVF_ne_u, STBVF_ne_u, },
+      { STBVF_eu  , STBVF_ne_u, STBVF_ne_u, STBVF_ne_u, },
+      { STBVF_eu  , STBVF_ne_u, STBVF_ne_u, STBVF_ne_u, },
+   },{
+      { STBVF_sw_u, STBVF_wu  , STBVF_nw_u, STBVF_nw_u, },
+      { STBVF_su  , STBVF_u   , STBVF_nu  , STBVF_nu  , },
+      { STBVF_eu  , STBVF_eu  , STBVF_ne_u, STBVF_ne_u, },
+      { STBVF_eu  , STBVF_eu  , STBVF_ne_u, STBVF_ne_u, },
+   },{
+      { STBVF_sw_u, STBVF_sw_u, STBVF_wu  , STBVF_nw_u, },
+      { STBVF_sw_u, STBVF_sw_u, STBVF_wu  , STBVF_nw_u, },
+      { STBVF_su  , STBVF_su  , STBVF_u   , STBVF_nu  , },
+      { STBVF_su  , STBVF_eu  , STBVF_eu  , STBVF_ne_u, },
+   },{
+      { STBVF_sw_u, STBVF_sw_u, STBVF_sw_u, STBVF_wu  , },
+      { STBVF_sw_u, STBVF_sw_u, STBVF_sw_u, STBVF_wu  , },
+      { STBVF_sw_u, STBVF_sw_u, STBVF_sw_u, STBVF_wu  , },
+      { STBVF_su  , STBVF_su  , STBVF_su  , STBVF_u   , },
+   }
+};
+#endif
 
-// the vertex heights of the block types, in binary vertex order (zyx):
-// lower: SW, SE, NW, NE; upper: SW, SE, NW, NE
-static stbvox_mesh_vertex stbvox_geometry_vheight[8][8];
+void stbvox_get_quad_vertex_pointer(stbvox_mesh_maker *mm, int mesh, stbvox_mesh_vertex **vertices, stbvox_mesh_face face)
+{
+   char *p = mm->output_cur[mesh][0];
+   int step = mm->output_step[mesh][0];
 
-// rotate vertices defined as [z][y][x] coords
-static unsigned char stbvox_rotate_vertex[8][4];
+   // allocate a new quad from the mesh
+   vertices[0] = (stbvox_mesh_vertex *) p; p += step;
+   vertices[1] = (stbvox_mesh_vertex *) p; p += step;
+   vertices[2] = (stbvox_mesh_vertex *) p; p += step;
+   vertices[3] = (stbvox_mesh_vertex *) p; p += step;
+   mm->output_cur[mesh][0] = p;
 
-#ifdef STBVOX_OPTIMIZED_VHEIGHT
-static stbvox_optimized_face_up_normal[4][4][4][4];
-#else
-static stbvox_planar_face_up_normal[4][4][4];
-#endif
+   // output the face
+   #ifdef STBVOX_ICONFIG_FACE_ATTRIBUTE
+      // write face as interleaved vertex data
+      *(stbvox_mesh_face *) (vertices[0]+1) = face;
+      *(stbvox_mesh_face *) (vertices[1]+1) = face;
+      *(stbvox_mesh_face *) (vertices[2]+1) = face;
+      *(stbvox_mesh_face *) (vertices[3]+1) = face;
+   #else
+      *(stbvox_mesh_face *) mm->output_cur[mesh][1] = face;
+      mm->output_cur[mesh][1] += 4;
+   #endif
+}
 
-static void stbvox_make_mesh_for_block_with_geo(stbvox_mesh_maker *mm, stbvox_pos pos, int v_off)
+void stbvox_make_mesh_for_face(stbvox_mesh_maker *mm, stbvox_rotate rot, int face, int v_off, stbvox_pos pos, stbvox_mesh_vertex vertbase, stbvox_mesh_vertex *face_coord, unsigned char mesh, int normal)
 {
-   int ns_off = mm->y_stride_in_bytes;
-   int ew_off = mm->x_stride_in_bytes;
-   int visible_faces, visible_base;
-   unsigned char mesh;
-
-   // first gather the geometry info for this block and all neighbors
+   stbvox_mesh_face face_data = stbvox_compute_mesh_face_value(mm,rot,face,v_off, normal);
 
-   unsigned char bt, nbt[6];
-   unsigned char geo, ngeo[6];
-   unsigned char rot, nrot[6];
+   // still need to compute ao & texlerp for each vertex
 
-   bt = mm->input.blocktype[v_off];
-   nbt[0] = mm->input.blocktype[v_off + ew_off];
-   nbt[1] = mm->input.blocktype[v_off + ns_off];
-   nbt[2] = mm->input.blocktype[v_off - ew_off];
-   nbt[3] = mm->input.blocktype[v_off - ns_off];
-   nbt[4] = mm->input.blocktype[v_off +      1];
-   nbt[5] = mm->input.blocktype[v_off -      1];
-   if (mm->input.geometry) {
-      int i;
-      geo = mm->input.geometry[v_off];
-      ngeo[0] = mm->input.geometry[v_off + ew_off];
-      ngeo[1] = mm->input.geometry[v_off + ns_off];
-      ngeo[2] = mm->input.geometry[v_off - ew_off];
-      ngeo[3] = mm->input.geometry[v_off - ns_off];
-      ngeo[4] = mm->input.geometry[v_off +      1];
-      ngeo[5] = mm->input.geometry[v_off -      1];
+   // first compute texlerp into p1
+   stbvox_mesh_vertex p1[4] = { 0 };
 
-      #ifndef STBVOX_ROTATION_IN_LIGHTING
-      rot = (geo >> 4) & 3;
-      geo &= 15;
-      for (i=0; i < 6; ++i) {
-         nrot[i] = (ngeo[i] >> 4) & 3;
-         ngeo[i] &= 15;
-      }
-      #endif
-      STBVOX_NOTUSED(i);
-   } else {
-      int i;
-      assert(mm->input.block_geometry);
-      geo = mm->input.block_geometry[bt];
-      for (i=0; i < 6; ++i)
-         ngeo[i] = mm->input.block_geometry[nbt[i]];
-      if (mm->input.selector) {
-         #ifndef STBVOX_ROTATION_IN_LIGHTING
-         rot     = (mm->input.selector[v_off         ] >> 4) & 3;
-         nrot[0] = (mm->input.selector[v_off + ew_off] >> 4) & 3;
-         nrot[1] = (mm->input.selector[v_off + ns_off] >> 4) & 3;
-         nrot[2] = (mm->input.selector[v_off - ew_off] >> 4) & 3;
-         nrot[3] = (mm->input.selector[v_off - ns_off] >> 4) & 3;
-         nrot[4] = (mm->input.selector[v_off +      1] >> 4) & 3;
-         nrot[5] = (mm->input.selector[v_off -      1] >> 4) & 3;
-         #endif
+   if (mm->input.block_texlerp) {
+      stbvox_block_type bt = mm->input.blocktype[v_off];
+      unsigned char val = mm->input.block_texlerp[bt];
+      p1[0] = p1[1] = p1[2] = p1[3] = stbvox_vertex_encode(0,0,0,0,val);
+   } else if (mm->input.block_texlerp_face) {
+      stbvox_block_type bt = mm->input.blocktype[v_off];
+      unsigned char bt_face = STBVOX_ROTATE(face, rot.block);
+      unsigned char val = mm->input.block_texlerp_face[bt][bt_face];
+      p1[0] = p1[1] = p1[2] = p1[3] = stbvox_vertex_encode(0,0,0,0,val);
+   } else if (mm->input.texlerp_face3) {
+      unsigned char val = (mm->input.texlerp_face3[v_off] >> stbvox_face3_lerp[face]) & 7;
+      if (face >= STBVOX_FACE_up)
+         val = stbvox_face3_updown[val];
+      p1[0] = p1[1] = p1[2] = p1[3] = stbvox_vertex_encode(0,0,0,0,val);
+   } else if (mm->input.texlerp_simple) {
+      unsigned char val = mm->input.texlerp_simple[v_off];
+      unsigned char lerp_face = (val >> 2) & 7;
+      if (lerp_face == face) {
+         p1[0] = (mm->input.texlerp_simple[v_off + mm->cube_vertex_offset[face][0]] >> 5) & 7;
+         p1[1] = (mm->input.texlerp_simple[v_off + mm->cube_vertex_offset[face][1]] >> 5) & 7;
+         p1[2] = (mm->input.texlerp_simple[v_off + mm->cube_vertex_offset[face][2]] >> 5) & 7;
+         p1[3] = (mm->input.texlerp_simple[v_off + mm->cube_vertex_offset[face][3]] >> 5) & 7;
+         p1[0] = stbvox_vertex_encode(0,0,0,0,p1[0]);
+         p1[1] = stbvox_vertex_encode(0,0,0,0,p1[1]);
+         p1[2] = stbvox_vertex_encode(0,0,0,0,p1[2]);
+         p1[3] = stbvox_vertex_encode(0,0,0,0,p1[3]);
       } else {
-         #ifndef STBVOX_ROTATION_IN_LIGHTING
-         rot = (geo>>4)&3;
-         geo &= 15;
-         for (i=0; i < 6; ++i) {
-            nrot[i] = (ngeo[i]>>4)&3;
-            ngeo[i] &= 15;
+         unsigned char base = stbvox_vert_lerp_for_simple[val&3];
+         p1[0] = p1[1] = p1[2] = p1[3] = stbvox_vertex_encode(0,0,0,0,base);
+      }
+   } else if (mm->input.texlerp) {
+      unsigned char facelerp = (mm->input.texlerp[v_off] >> stbvox_face_lerp[face]) & 3;
+      if (facelerp == STBVOX_TEXLERP_FACE_use_vert) {
+         if (mm->input.texlerp_vert3 && face != STBVOX_FACE_down) {
+            unsigned char shift = stbvox_vert3_lerp[face];
+            p1[0] = (mm->input.texlerp_vert3[mm->cube_vertex_offset[face][0]] >> shift) & 7;
+            p1[1] = (mm->input.texlerp_vert3[mm->cube_vertex_offset[face][1]] >> shift) & 7;
+            p1[2] = (mm->input.texlerp_vert3[mm->cube_vertex_offset[face][2]] >> shift) & 7;
+            p1[3] = (mm->input.texlerp_vert3[mm->cube_vertex_offset[face][3]] >> shift) & 7;
+         } else {
+            p1[0] = stbvox_vert_lerp_for_simple[mm->input.texlerp[mm->cube_vertex_offset[face][0]]>>6];
+            p1[1] = stbvox_vert_lerp_for_simple[mm->input.texlerp[mm->cube_vertex_offset[face][1]]>>6];
+            p1[2] = stbvox_vert_lerp_for_simple[mm->input.texlerp[mm->cube_vertex_offset[face][2]]>>6];
+            p1[3] = stbvox_vert_lerp_for_simple[mm->input.texlerp[mm->cube_vertex_offset[face][3]]>>6];
          }
-         #endif
+         p1[0] = stbvox_vertex_encode(0,0,0,0,p1[0]);
+         p1[1] = stbvox_vertex_encode(0,0,0,0,p1[1]);
+         p1[2] = stbvox_vertex_encode(0,0,0,0,p1[2]);
+         p1[3] = stbvox_vertex_encode(0,0,0,0,p1[3]);
+      } else {
+         p1[0] = p1[1] = p1[2] = p1[3] = stbvox_vertex_encode(0,0,0,0,stbvox_vert_lerp_for_face_lerp[facelerp]);
       }
+   } else {
+      p1[0] = p1[1] = p1[2] = p1[3] = stbvox_vertex_encode(0,0,0,0,7);
    }
 
-   #ifdef STBVOX_ROTATION_IN_LIGHTING
-   rot = mm->input.lighting[v_off] & 3;
-   nrot[0] = (mm->input.lighting[v_off + ew_off]) & 3;
-   nrot[1] = (mm->input.lighting[v_off + ns_off]) & 3;
-   nrot[2] = (mm->input.lighting[v_off - ew_off]) & 3;
-   nrot[3] = (mm->input.lighting[v_off - ns_off]) & 3;
-   nrot[4] = (mm->input.lighting[v_off +      1]) & 3;
-   nrot[5] = (mm->input.lighting[v_off -      1]) & 3;
-   #endif
-
-   if (geo == STBVOX_GEOM_transp) {
-      // transparency has a special rule: if the blocktype is the same,
-      // and the faces are compatible, then can hide them; otherwise,
-      // force them on
-      // Note that this means we don't support any transparentshapes other
-      // than solid blocks, since detecting them is too complicated. If
-      // you wanted to do something like minecraft water, you probably
-      // should just do that with a separate renderer anyway. (We don't
-      // support transparency sorting so you need to use alpha test
-      // anyway)
-      int i;
-      for (i=0; i < 6; ++i)
-         if (nbt[i] != bt) {
-            nbt[i] = 0;
-            ngeo[i] = STBVOX_GEOM_empty;
-         } else
-            ngeo[i] = STBVOX_GEOM_solid;
-      geo = STBVOX_GEOM_solid;
-   }
-
-   // now compute the face visibility
-   visible_base = stbvox_hasface[geo][rot];
-   // @TODO: assert(visible_base != 0); // we should have early-outted earlier in this case
-   visible_faces = 0;
+   {
+      stbvox_mesh_vertex *mv[4];
+      stbvox_get_quad_vertex_pointer(mm, mesh, mv, face_data);
 
-   // now, for every face that might be visible, check if neighbor hides it
-   if (visible_base & (1 << STBVOX_FACE_east)) {
-      int  type = stbvox_facetype[ geo   ][(STBVOX_FACE_east+ rot   )&3];
-      int ntype = stbvox_facetype[ngeo[0]][(STBVOX_FACE_west+nrot[0])&3];
-      visible_faces |= ((stbvox_face_visible[type]) >> (ntype + 5 - STBVOX_FACE_east)) & (1 << STBVOX_FACE_east);
-   }
-   if (visible_base & (1 << STBVOX_FACE_north)) {
-      int  type = stbvox_facetype[ geo   ][(STBVOX_FACE_north+ rot   )&3];
-      int ntype = stbvox_facetype[ngeo[1]][(STBVOX_FACE_south+nrot[1])&3];
-      visible_faces |= ((stbvox_face_visible[type]) >> (ntype + 5 - STBVOX_FACE_north)) & (1 << STBVOX_FACE_north);
-   }
-   if (visible_base & (1 << STBVOX_FACE_west)) {
-      int  type = stbvox_facetype[ geo   ][(STBVOX_FACE_west+ rot   )&3];
-      int ntype = stbvox_facetype[ngeo[2]][(STBVOX_FACE_east+nrot[2])&3];
-      visible_faces |= ((stbvox_face_visible[type]) >> (ntype + 5 - STBVOX_FACE_west)) & (1 << STBVOX_FACE_west);
-   }
-   if (visible_base & (1 << STBVOX_FACE_south)) {
-      int  type = stbvox_facetype[ geo   ][(STBVOX_FACE_south+ rot   )&3];
-      int ntype = stbvox_facetype[ngeo[3]][(STBVOX_FACE_north+nrot[3])&3];
-      visible_faces |= ((stbvox_face_visible[type]) >> (ntype + 5 - STBVOX_FACE_south)) & (1 << STBVOX_FACE_south);
-   }
-   if (visible_base & (1 << STBVOX_FACE_up)) {
-      int  type = stbvox_facetype[ geo   ][STBVOX_FACE_up];
-      int ntype = stbvox_facetype[ngeo[4]][STBVOX_FACE_down];
-      visible_faces |= ((stbvox_face_visible[type]) >> (ntype + 5 - STBVOX_FACE_up)) & (1 << STBVOX_FACE_up);
-   }
-   if (visible_base & (1 << STBVOX_FACE_down)) {
-      int  type = stbvox_facetype[ geo   ][STBVOX_FACE_down];
-      int ntype = stbvox_facetype[ngeo[5]][STBVOX_FACE_up];
-      visible_faces |= ((stbvox_face_visible[type]) >> (ntype + 5 - STBVOX_FACE_down)) & (1 << STBVOX_FACE_down);
-   }
+      if (mm->input.lighting) {
+         // @TODO: lighting at block centers, but not gathered, instead constant-per-face
+         if (mm->input.lighting_at_vertices) {
+            int i;
+            for (i=0; i < 4; ++i) {
+               *mv[i] = vertbase + face_coord[i]
+                          + stbvox_vertex_encode(0,0,0,mm->input.lighting[v_off + mm->cube_vertex_offset[face][i]] & 63,0)
+                          + p1[i];
+            }
+         } else {
+            unsigned char *amb = &mm->input.lighting[v_off];
+            int i,j;
+            #ifdef STBVOX_ROTATION_IN_LIGHTING
+            #define STBVOX_GET_LIGHTING(light) ((light) & ~3)
+            #define STBVOX_LIGHTING_ROUNDOFF   8
+            #else
+            #define STBVOX_GET_LIGHTING(light) (light)
+            #define STBVOX_LIGHTING_ROUNDOFF   2
+            #endif
 
-   if (geo == STBVOX_GEOM_force)
-      geo = STBVOX_GEOM_solid;
+            for (i=0; i < 4; ++i) {
+               // for each vertex, gather from the four neighbor blocks it's facing
+               unsigned char *vamb = &amb[mm->cube_vertex_offset[face][i]];
+               int total=0;
+               for (j=0; j < 4; ++j)
+                  total += STBVOX_GET_LIGHTING(vamb[mm->vertex_gather_offset[face][j]]);
+               *mv[i] = vertbase + face_coord[i]
+                          + stbvox_vertex_encode(0,0,0,(total+STBVOX_LIGHTING_ROUNDOFF)>>4,0)
+                          + p1[i];
+                          // >> 4 is because:
+                          //   >> 2 to divide by 4 to get average over 4 samples
+                          //   >> 2 because input is 8 bits, output is 6 bits
+            }
 
-   assert((geo == STBVOX_GEOM_crossed_pair) ? (visible_faces == 15) : 1);
+            // @TODO: note that gathering baked *lighting*
+            // is different from gathering baked ao; baked ao can count
+            // solid blocks as 0 ao, but baked lighting wants average
+            // of non-blocked--not take average & treat blocked as 0. And
+            // we can't bake the right value into the solid blocks
+            // because they can have different lighting values on
+            // different sides. So we need to actually gather and
+            // then divide by 0..4 (which we can do with a table-driven
+            // multiply, or have an 'if' for the 3 case)
 
-   // now we finally know for sure which faces are getting generated
-   if (visible_faces == 0)
-      return;
+         }
+      } else {
+         vertbase += stbvox_vertex_encode(0,0,0,63,0);
+         *mv[0] = vertbase + face_coord[0] + p1[0];
+         *mv[1] = vertbase + face_coord[1] + p1[1];
+         *mv[2] = vertbase + face_coord[2] + p1[2];
+         *mv[3] = vertbase + face_coord[3] + p1[3];
+      }
+   }
+}
 
-   mesh = mm->default_mesh;
-   if (mm->input.selector)
-      mesh = mm->input.selector[v_off];
+#ifndef STBVOX_OPTIMIZED_VHEIGHT
+// get opposite-facing normal & texgen for opposite face, used to map up-facing vheight data to down-facing data
+static unsigned char stbvox_reverse_face[STBVF_count] =
+{
+   STBVF_w, STBVF_s, STBVF_e, STBVF_n, STBVF_d   , STBVF_u   , STBVF_wd, STBVF_wu,
+         0,       0,       0,       0, STBVF_sw_d, STBVF_sw_u, STBVF_sd, STBVF_su,
+         0,       0,       0,       0, STBVF_se_d, STBVF_se_u, STBVF_ed, STBVF_eu,
+         0,       0,       0,       0, STBVF_ne_d, STBVF_ne_d, STBVF_nd, STBVF_nu
+};
 
-   if (geo <= STBVOX_GEOM_ceil_slope_north_is_bottom) {
-      // this is the simple case, we can just use regular block gen with special vmesh calculated with vheight
-      stbvox_mesh_vertex basevert;
-      stbvox_mesh_vertex vmesh[6][4];
-      stbvox_rotate rotate = { 0,0,0,0 };
-      unsigned char simple_rot = rot;
-      int i;
-      // we only need to do this for the displayed faces, but it's easier
-      // to just do it up front; @OPTIMIZE check if it's faster to do it
-      // for visible faces only
-      for (i=0; i < 6*4; ++i) {
-         int vert = stbvox_vertex_selector[0][i];
-         vert = stbvox_rotate_vertex[vert][rot];
-         vmesh[0][i] = stbvox_vmesh_pre_vheight[0][i]
-                     + stbvox_geometry_vheight[geo][vert];
-      }
 
-      basevert = stbvox_vertex_encode(pos.x, pos.y, pos.z << STBVOX_CONFIG_PRECISION_Z, 0,0);
-      if (mm->input.selector) {
-         mesh = mm->input.selector[v_off];
-      }
+// render non-planar quads by splitting into two triangles, rendering each as a degenerate quad
+static void stbvox_make_12_split_mesh_for_face(stbvox_mesh_maker *mm, stbvox_rotate rot, int face, int v_off, stbvox_pos pos, stbvox_mesh_vertex vertbase, stbvox_mesh_vertex *face_coord, unsigned char mesh, unsigned char *ht)
+{
+   stbvox_mesh_vertex v[4];
 
-      // check if we're going off the end
-      if (mm->output_cur[mesh][0] + mm->output_size[mesh][0]*6 > mm->output_end[mesh][0]) {
-         mm->full = 1;
-         return;
-      }
+   unsigned char normal1 = stbvox_face_up_normal_012[ht[2]][ht[1]][ht[0]];
+   unsigned char normal2 = stbvox_face_up_normal_123[ht[3]][ht[2]][ht[1]];
 
-      if (geo >= STBVOX_GEOM_floor_slope_north_is_top) {
-         if (visible_faces & (1 << STBVOX_FACE_up)) {
-            int normal = geo == STBVOX_GEOM_floor_slope_north_is_top ? stbvox_floor_slope_for_rot[simple_rot] : STBVOX_FACE_up;
-            rotate.facerot = simple_rot;
-            stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_up  , v_off, pos, basevert, vmesh[STBVOX_FACE_up], mesh, normal);
-         }
-         if (visible_faces & (1 << STBVOX_FACE_down)) {
-            int normal = geo == STBVOX_GEOM_ceil_slope_north_is_bottom ? stbvox_ceil_slope_for_rot[simple_rot] : STBVOX_FACE_down;
-            rotate.facerot = (-rotate.facerot) & 3;
-            stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_down, v_off, pos, basevert, vmesh[STBVOX_FACE_down], mesh, normal);
-         }
-      } else {
-         if (visible_faces & (1 << STBVOX_FACE_up)) {
-            rotate.facerot = simple_rot;
-            stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_up  , v_off, pos, basevert, vmesh[STBVOX_FACE_up], mesh, STBVOX_FACE_up);
-         }
-         if (visible_faces & (1 << STBVOX_FACE_down)) {
-            rotate.facerot = (-rotate.facerot) & 3;
-            stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_down, v_off, pos, basevert, vmesh[STBVOX_FACE_down], mesh, STBVOX_FACE_down);
-         }
-      }
+   if (face == STBVOX_FACE_down) {
+      normal1 = stbvox_reverse_face[normal1];
+      normal2 = stbvox_reverse_face[normal2];
+   }
 
-      if (mm->input.rotate) {
-         unsigned char val = mm->input.rotate[v_off];
-         rotate.block   = (val >> 0) & 3;
-         rotate.overlay = (val >> 2) & 3;
-         //rotate.tex2    = (val >> 4) & 3;
-         rotate.ecolor  = (val >> 6) & 3;
-      } else {
-         rotate.block = rotate.overlay = rotate.ecolor = simple_rot;
-      }
+   // the floor side face_coord is stored in order NW,NE,SE,SW, but ht[] is stored SW,SE,NW,NE
+   v[0] = face_coord[2];
+   v[1] = face_coord[3];
+   v[2] = face_coord[0];
+   v[3] = face_coord[2];
+   stbvox_make_mesh_for_face(mm, rot, face, v_off, pos, vertbase, v, mesh, normal1);
+   v[1] = face_coord[0];
+   v[2] = face_coord[1];
+   stbvox_make_mesh_for_face(mm, rot, face, v_off, pos, vertbase, v, mesh, normal2);
+}
 
-      rotate.facerot = 0;
+static void stbvox_make_03_split_mesh_for_face(stbvox_mesh_maker *mm, stbvox_rotate rot, int face, int v_off, stbvox_pos pos, stbvox_mesh_vertex vertbase, stbvox_mesh_vertex *face_coord, unsigned char mesh, unsigned char *ht)
+{
+   stbvox_mesh_vertex v[4];
 
-      if (visible_faces & (1 << STBVOX_FACE_north))
-         stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_north, v_off, pos, basevert, vmesh[STBVOX_FACE_north], mesh, STBVOX_FACE_north);
-      if (visible_faces & (1 << STBVOX_FACE_south))
-         stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_south, v_off, pos, basevert, vmesh[STBVOX_FACE_south], mesh, STBVOX_FACE_south);
-      if (visible_faces & (1 << STBVOX_FACE_east))
-         stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_east , v_off, pos, basevert, vmesh[STBVOX_FACE_east ], mesh, STBVOX_FACE_east);
-      if (visible_faces & (1 << STBVOX_FACE_west))
-         stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_west , v_off, pos, basevert, vmesh[STBVOX_FACE_west ], mesh, STBVOX_FACE_west);
+   unsigned char normal1 = stbvox_face_up_normal_013[ht[3]][ht[1]][ht[0]];
+   unsigned char normal2 = stbvox_face_up_normal_023[ht[3]][ht[2]][ht[0]];
+
+   if (face == STBVOX_FACE_down) {
+      normal1 = stbvox_reverse_face[normal1];
+      normal2 = stbvox_reverse_face[normal2];
    }
-   if (geo >= STBVOX_GEOM_floor_vheight_03) {
-      // this case can also be generated with regular block gen with special vmesh,
-      // except:
-      //     if we want to generate middle diagonal for 'weird' blocks
-      //     it's more complicated to detect neighbor matchups
-      stbvox_mesh_vertex vmesh[6][4];
-      stbvox_mesh_vertex cube[8];
-      stbvox_mesh_vertex basevert;
-      stbvox_rotate rotate = { 0,0,0,0 };
-      unsigned char simple_rot = rot;
-      unsigned char ht[4];
-      int extreme;
 
-      // extract the heights
-      if (mm->input.vheight) {
-         unsigned char v =  mm->input.vheight[v_off];
-         ht[0] = (v >> 0) & 3;
-         ht[1] = (v >> 2) & 3;
-         ht[2] = (v >> 4) & 3;
-         ht[3] = (v >> 6) & 3;
-      } else if (mm->input.block_vheight) {
-         unsigned char v = mm->input.block_vheight[bt];
-         unsigned char raw[4];
-         int i;
+   v[0] = face_coord[1];
+   v[1] = face_coord[2];
+   v[2] = face_coord[3];
+   v[3] = face_coord[1];
+   stbvox_make_mesh_for_face(mm, rot, face, v_off, pos, vertbase, v, mesh, normal1);
+   v[1] = face_coord[3];
+   v[2] = face_coord[0];
+   stbvox_make_mesh_for_face(mm, rot, face, v_off, pos, vertbase, v, mesh, normal2);  // this one is correct!
+}
+#endif
 
-         raw[0] = (v >> 0) & 3;
-         raw[1] = (v >> 2) & 3;
-         raw[2] = (v >> 4) & 3;
-         raw[3] = (v >> 6) & 3;
+#ifndef STBVOX_CONFIG_PRECISION_Z
+#define STBVOX_CONFIG_PRECISION_Z 1
+#endif
 
-         for (i=0; i < 4; ++i)
-            ht[i] = raw[stbvox_rotate_vertex[i][rot]];
-      } else {
-         assert(0);
-      }
+// simple case for mesh generation: we have only solid and empty blocks
+static void stbvox_make_mesh_for_block(stbvox_mesh_maker *mm, stbvox_pos pos, int v_off, stbvox_mesh_vertex *vmesh)
+{
+   int ns_off = mm->y_stride_in_bytes;
+   int ew_off = mm->x_stride_in_bytes;
 
-      // flag whether any sides go off the top of the block, which means
-      // our visible_faces test was wrong
-      extreme = (ht[0] == 3 || ht[1] == 3 || ht[2] == 3 || ht[3] == 3);
+   unsigned char *blockptr = &mm->input.blocktype[v_off];
+   stbvox_mesh_vertex basevert = stbvox_vertex_encode(pos.x, pos.y, pos.z << STBVOX_CONFIG_PRECISION_Z , 0,0);
 
-      if (geo >= STBVOX_GEOM_ceil_vheight_03) {
-         cube[0] = stbvox_vertex_encode(0,0,ht[0],0,0);
-         cube[1] = stbvox_vertex_encode(0,0,ht[1],0,0);
-         cube[2] = stbvox_vertex_encode(0,0,ht[2],0,0);
-         cube[3] = stbvox_vertex_encode(0,0,ht[3],0,0);
-         cube[4] = stbvox_vertex_encode(0,0,2,0,0);
-         cube[5] = stbvox_vertex_encode(0,0,2,0,0);
-         cube[6] = stbvox_vertex_encode(0,0,2,0,0);
-         cube[7] = stbvox_vertex_encode(0,0,2,0,0);
-      } else {
-         cube[0] = stbvox_vertex_encode(0,0,0,0,0);
-         cube[1] = stbvox_vertex_encode(0,0,0,0,0);
-         cube[2] = stbvox_vertex_encode(0,0,0,0,0);
-         cube[3] = stbvox_vertex_encode(0,0,0,0,0);
-         cube[4] = stbvox_vertex_encode(0,0,ht[0],0,0);
-         cube[5] = stbvox_vertex_encode(0,0,ht[1],0,0);
-         cube[6] = stbvox_vertex_encode(0,0,ht[2],0,0);
-         cube[7] = stbvox_vertex_encode(0,0,ht[3],0,0);
-      }
-      if (!mm->input.vheight && mm->input.block_vheight) {
-         // @TODO: support block vheight here, I've forgotten what needs to be done specially
-      }
+   stbvox_rotate rot = { 0,0,0,0 };
+   unsigned char simple_rot = 0;
 
-      // build vertex mesh
-      {
-         int i;
-         for (i=0; i < 6*4; ++i) {
-            int vert = stbvox_vertex_selector[0][i];
-            vmesh[0][i] = stbvox_vmesh_pre_vheight[0][i]
-                        + cube[vert];
-         }
-      }
+   unsigned char mesh = mm->default_mesh;
 
-      basevert = stbvox_vertex_encode(pos.x, pos.y, pos.z << STBVOX_CONFIG_PRECISION_Z, 0,0);
-      // check if we're going off the end
-      if (mm->output_cur[mesh][0] + mm->output_size[mesh][0]*6 > mm->output_end[mesh][0]) {
-         mm->full = 1;
-         return;
-      }
+   if (mm->input.selector)
+      mesh = mm->input.selector[v_off];
 
-      // @TODO generate split faces
-      if (visible_faces & (1 << STBVOX_FACE_up)) {
-         if (geo >= STBVOX_GEOM_ceil_vheight_03)
-            // flat
-            stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_up  , v_off, pos, basevert, vmesh[STBVOX_FACE_up], mesh, STBVOX_FACE_up);
-         else {
-         #ifndef STBVOX_OPTIMIZED_VHEIGHT
-            // check if it's non-planar
-            if (cube[5] + cube[6] != cube[4] + cube[7]) {
-               // not planar, split along diagonal and make degenerate quads
-               if (geo == STBVOX_GEOM_floor_vheight_03)
-                  stbvox_make_03_split_mesh_for_face(mm, rotate, STBVOX_FACE_up, v_off, pos, basevert, vmesh[STBVOX_FACE_up], mesh, ht);
-               else
-                  stbvox_make_12_split_mesh_for_face(mm, rotate, STBVOX_FACE_up, v_off, pos, basevert, vmesh[STBVOX_FACE_up], mesh, ht);
-            } else
-               stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_up  , v_off, pos, basevert, vmesh[STBVOX_FACE_up], mesh, stbvox_planar_face_up_normal[ht[2]][ht[1]][ht[0]]);
-         #else
-            stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_up  , v_off, pos, basevert, vmesh[STBVOX_FACE_up], mesh, stbvox_optimized_face_up_normal[ht[3]][ht[2]][ht[1]][ht[0]]);
-         #endif
-         }
-      }
-      if (visible_faces & (1 << STBVOX_FACE_down)) {
-         if (geo < STBVOX_GEOM_ceil_vheight_03)
-            // flat
-            stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_down, v_off, pos, basevert, vmesh[STBVOX_FACE_down], mesh, STBVOX_FACE_down);
-         else {
-         #ifndef STBVOX_OPTIMIZED_VHEIGHT
-            // check if it's non-planar
-            if (cube[1] + cube[2] != cube[0] + cube[3]) {
-               // not planar, split along diagonal and make degenerate quads
-               if (geo == STBVOX_GEOM_ceil_vheight_03)
-                  stbvox_make_03_split_mesh_for_face(mm, rotate, STBVOX_FACE_down, v_off, pos, basevert, vmesh[STBVOX_FACE_down], mesh, ht);
-               else
-                  stbvox_make_12_split_mesh_for_face(mm, rotate, STBVOX_FACE_down, v_off, pos, basevert, vmesh[STBVOX_FACE_down], mesh, ht);
-            } else
-               stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_down, v_off, pos, basevert, vmesh[STBVOX_FACE_down], mesh, stbvox_reverse_face[stbvox_planar_face_up_normal[ht[2]][ht[1]][ht[0]]]);
-         #else
-            stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_down, v_off, pos, basevert, vmesh[STBVOX_FACE_down], mesh, stbvox_reverse_face[stbvox_optimized_face_up_normal[ht[3]][ht[2]][ht[1]][ht[0]]]);
-         #endif
-         }
-      }
+   // check if we're going off the end
+   if (mm->output_cur[mesh][0] + mm->output_size[mesh][0]*6 > mm->output_end[mesh][0]) {
+      mm->full = 1;
+      return;
+   }
 
-      if (mm->input.rotate) {
-         unsigned char val = mm->input.rotate[v_off];
-         rotate.block   = (val >> 0) & 3;
-         rotate.overlay = (val >> 2) & 3;
-         //rotate.tex2    = (val >> 4) & 3;
-         rotate.ecolor  = (val >> 6) & 3;
-      } else if (mm->input.selector) {
-         rotate.block = rotate.overlay = rotate.ecolor = simple_rot;
-      }
+   #ifdef STBVOX_ROTATION_IN_LIGHTING
+   simple_rot = mm->input.lighting[v_off] & 3;
+   #endif
 
-      if ((visible_faces & (1 << STBVOX_FACE_north)) || (extreme && (ht[2] == 3 || ht[3] == 3)))
-         stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_north, v_off, pos, basevert, vmesh[STBVOX_FACE_north], mesh, STBVOX_FACE_north);
-      if ((visible_faces & (1 << STBVOX_FACE_south)) || (extreme && (ht[0] == 3 || ht[1] == 3))) 
-         stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_south, v_off, pos, basevert, vmesh[STBVOX_FACE_south], mesh, STBVOX_FACE_south);
-      if ((visible_faces & (1 << STBVOX_FACE_east)) || (extreme && (ht[1] == 3 || ht[3] == 3)))
-         stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_east , v_off, pos, basevert, vmesh[STBVOX_FACE_east ], mesh, STBVOX_FACE_east);
-      if ((visible_faces & (1 << STBVOX_FACE_west)) || (extreme && (ht[0] == 3 || ht[2] == 3)))
-         stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_west , v_off, pos, basevert, vmesh[STBVOX_FACE_west ], mesh, STBVOX_FACE_west);
+   if (blockptr[ 1]==0) {
+      rot.facerot = simple_rot;
+      stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_up  , v_off, pos, basevert, vmesh+4*STBVOX_FACE_up, mesh, STBVOX_FACE_up);
+   }
+   if (blockptr[-1]==0) {
+      rot.facerot = (-simple_rot) & 3;
+      stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_down, v_off, pos, basevert, vmesh+4*STBVOX_FACE_down, mesh, STBVOX_FACE_down);
    }
 
-   if (geo == STBVOX_GEOM_crossed_pair) {
-      // this can be generated with a special vmesh
-      stbvox_mesh_vertex basevert = stbvox_vertex_encode(pos.x, pos.y, pos.z << STBVOX_CONFIG_PRECISION_Z , 0,0);
-      unsigned char simple_rot=0;
-      stbvox_rotate rot = { 0,0,0,0 };
-      unsigned char mesh = mm->default_mesh;
-      if (mm->input.selector) {
-         mesh = mm->input.selector[v_off];
-         simple_rot = mesh >> 4;
-         mesh &= 15;
-      }
-
-      // check if we're going off the end
-      if (mm->output_cur[mesh][0] + mm->output_size[mesh][0]*4 > mm->output_end[mesh][0]) {
-         mm->full = 1;
-         return;
-      }
+   if (mm->input.rotate) {
+      unsigned char val = mm->input.rotate[v_off];
+      rot.block   = (val >> 0) & 3;
+      rot.overlay = (val >> 2) & 3;
+      //rot.tex2    = (val >> 4) & 3;
+      rot.ecolor  = (val >> 6) & 3;
+   } else {
+      rot.block = rot.overlay = rot.ecolor = simple_rot;
+   }
+   rot.facerot = 0;
 
-      if (mm->input.rotate) {
-         unsigned char val = mm->input.rotate[v_off];
-         rot.block   = (val >> 0) & 3;
-         rot.overlay = (val >> 2) & 3;
-         //rot.tex2    = (val >> 4) & 3;
-         rot.ecolor  = (val >> 6) & 3;
-      } else if (mm->input.selector) {
-         rot.block = rot.overlay = rot.ecolor = simple_rot;
-      }
-      rot.facerot = 0;
+   if (blockptr[ ns_off]==0)
+      stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_north, v_off, pos, basevert, vmesh+4*STBVOX_FACE_north, mesh, STBVOX_FACE_north);
+   if (blockptr[-ns_off]==0)
+      stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_south, v_off, pos, basevert, vmesh+4*STBVOX_FACE_south, mesh, STBVOX_FACE_south);
+   if (blockptr[ ew_off]==0)
+      stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_east , v_off, pos, basevert, vmesh+4*STBVOX_FACE_east, mesh, STBVOX_FACE_east);
+   if (blockptr[-ew_off]==0)
+      stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_west , v_off, pos, basevert, vmesh+4*STBVOX_FACE_west, mesh, STBVOX_FACE_west);
+}
 
-      stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_north, v_off, pos, basevert, stbvox_vmesh_crossed_pair[STBVOX_FACE_north], mesh, STBVF_ne_u_cross);
-      stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_south, v_off, pos, basevert, stbvox_vmesh_crossed_pair[STBVOX_FACE_south], mesh, STBVF_sw_u_cross);
-      stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_east , v_off, pos, basevert, stbvox_vmesh_crossed_pair[STBVOX_FACE_east ], mesh, STBVF_se_u_cross);
-      stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_west , v_off, pos, basevert, stbvox_vmesh_crossed_pair[STBVOX_FACE_west ], mesh, STBVF_nw_u_cross);
-   }
 
+// void stbvox_make_mesh_for_block_with_geo(stbvox_mesh_maker *mm, stbvox_pos pos, int v_off)
+//
+// complex case for mesh generation: we have lots of different
+// block types, and we don't want to generate faces of blocks
+// if they're hidden by neighbors.
+//
+// we use lots of tables to determine this: we have a table
+// which tells us what face type is generated for each type of
+// geometry, and then a table that tells us whether that type
+// is hidden by a neighbor.
 
-   // @TODO
-   // STBVOX_GEOM_floor_slope_north_is_top_as_wall,
-   // STBVOX_GEOM_ceil_slope_north_is_bottom_as_wall,
-}
 
-static void stbvox_make_mesh_for_column(stbvox_mesh_maker *mm, int x, int y, int z0)
+static void stbvox_make_mesh_for_block_with_geo(stbvox_mesh_maker *mm, stbvox_pos pos, int v_off)
 {
-   stbvox_pos pos = { x,y,0 };
-   int v_off = x * mm->x_stride_in_bytes + y * mm->y_stride_in_bytes;
    int ns_off = mm->y_stride_in_bytes;
    int ew_off = mm->x_stride_in_bytes;
+   int visible_faces, visible_base;
+   unsigned char mesh;
+
+   // first gather the geometry info for this block and all neighbors
+
+   unsigned char bt, nbt[6];
+   unsigned char geo, ngeo[6];
+   unsigned char rot, nrot[6];
+
+   bt = mm->input.blocktype[v_off];
+   nbt[0] = mm->input.blocktype[v_off + ew_off];
+   nbt[1] = mm->input.blocktype[v_off + ns_off];
+   nbt[2] = mm->input.blocktype[v_off - ew_off];
+   nbt[3] = mm->input.blocktype[v_off - ns_off];
+   nbt[4] = mm->input.blocktype[v_off +      1];
+   nbt[5] = mm->input.blocktype[v_off -      1];
    if (mm->input.geometry) {
-      unsigned char *bt  = mm->input.blocktype + v_off;
-      unsigned char *geo = mm->input.geometry + v_off;
-      int z;
-      for (z=z0; z < mm->z1; ++z) {
-         if (bt[z] && ( !bt[z+ns_off] || !STBVOX_GET_GEO(geo[z+ns_off]) || !bt[z-ns_off] || !STBVOX_GET_GEO(geo[z-ns_off])
-                      || !bt[z+ew_off] || !STBVOX_GET_GEO(geo[z+ew_off]) || !bt[z-ew_off] || !STBVOX_GET_GEO(geo[z-ew_off])))
-         {  // TODO check up and down
-            pos.z = z;
-            stbvox_make_mesh_for_block_with_geo(mm, pos, v_off+z);
-            if (mm->full) {
-               mm->cur_z = z;
-               return;
-            }
-         }
-      }
-   } else if (mm->input.block_geometry) {
-      int z;
-      unsigned char *bt  = mm->input.blocktype + v_off;
-      unsigned char *geo = mm->input.block_geometry;
-      for (z=z0; z < mm->z1; ++z) {
-         if (bt[z] && (    geo[bt[z+ns_off]] != STBVOX_GEOM_solid
-                        || geo[bt[z-ns_off]] != STBVOX_GEOM_solid
-                        || geo[bt[z+ew_off]] != STBVOX_GEOM_solid
-                        || geo[bt[z-ew_off]] != STBVOX_GEOM_solid
-                        || geo[bt[z-1]] != STBVOX_GEOM_solid
-                        || geo[bt[z+1]] != STBVOX_GEOM_solid))
-         {
-            pos.z = z;
-            stbvox_make_mesh_for_block_with_geo(mm, pos, v_off+z);
-            if (mm->full) {
-               mm->cur_z = z;
-               return;
-            }
-         }
+      int i;
+      geo = mm->input.geometry[v_off];
+      ngeo[0] = mm->input.geometry[v_off + ew_off];
+      ngeo[1] = mm->input.geometry[v_off + ns_off];
+      ngeo[2] = mm->input.geometry[v_off - ew_off];
+      ngeo[3] = mm->input.geometry[v_off - ns_off];
+      ngeo[4] = mm->input.geometry[v_off +      1];
+      ngeo[5] = mm->input.geometry[v_off -      1];
+
+      #ifndef STBVOX_ROTATION_IN_LIGHTING
+      rot = (geo >> 4) & 3;
+      geo &= 15;
+      for (i=0; i < 6; ++i) {
+         nrot[i] = (ngeo[i] >> 4) & 3;
+         ngeo[i] &= 15;
       }
-   } else {
-      unsigned char *bt = mm->input.blocktype + v_off;
-      int z;
-      #if STBVOX_CONFIG_PRECISION_Z == 1
-      stbvox_mesh_vertex *vmesh = stbvox_vmesh_delta_half_z[0];
-      #else
-      stbvox_mesh_vertex *vmesh = stbvox_vmesh_delta_normal[0];
       #endif
-      for (z=z0; z < mm->z1; ++z) {
-         // if it's solid and at least one neighbor isn't solid
-         if (bt[z] && (!bt[z+ns_off] || !bt[z-ns_off] || !bt[z+ew_off] || !bt[z-ew_off] || !bt[z-1] || !bt[z+1])) {
-            pos.z = z;
-            stbvox_make_mesh_for_block(mm, pos, v_off+z, vmesh);
-            if (mm->full) {
-               mm->cur_z = z;
-               return;
-            }
+      STBVOX_NOTUSED(i);
+   } else {
+      int i;
+      assert(mm->input.block_geometry);
+      geo = mm->input.block_geometry[bt];
+      for (i=0; i < 6; ++i)
+         ngeo[i] = mm->input.block_geometry[nbt[i]];
+      if (mm->input.selector) {
+         #ifndef STBVOX_ROTATION_IN_LIGHTING
+         rot     = (mm->input.selector[v_off         ] >> 4) & 3;
+         nrot[0] = (mm->input.selector[v_off + ew_off] >> 4) & 3;
+         nrot[1] = (mm->input.selector[v_off + ns_off] >> 4) & 3;
+         nrot[2] = (mm->input.selector[v_off - ew_off] >> 4) & 3;
+         nrot[3] = (mm->input.selector[v_off - ns_off] >> 4) & 3;
+         nrot[4] = (mm->input.selector[v_off +      1] >> 4) & 3;
+         nrot[5] = (mm->input.selector[v_off -      1] >> 4) & 3;
+         #endif
+      } else {
+         #ifndef STBVOX_ROTATION_IN_LIGHTING
+         rot = (geo>>4)&3;
+         geo &= 15;
+         for (i=0; i < 6; ++i) {
+            nrot[i] = (ngeo[i]>>4)&3;
+            ngeo[i] &= 15;
          }
+         #endif
       }
    }
-}
 
-static void stbvox_bring_up_to_date(stbvox_mesh_maker *mm)
-{
-   if (mm->config_dirty) {
-      int i;
-      #ifdef STBVOX_ICONFIG_FACE_ATTRIBUTE
-         mm->num_mesh_slots = 1;
-         for (i=0; i < STBVOX_MAX_MESHES; ++i) {
-            mm->output_size[i][0] = 32;
-            mm->output_step[i][0] = 8;
-         }
-      #else
-         mm->num_mesh_slots = 2;
-         for (i=0; i < STBVOX_MAX_MESHES; ++i) {
-            mm->output_size[i][0] = 16;
-            mm->output_step[i][0] = 4;
-            mm->output_size[i][1] = 4;
-            mm->output_step[i][1] = 4;
-         }
-      #endif
+   #ifdef STBVOX_ROTATION_IN_LIGHTING
+   rot = mm->input.lighting[v_off] & 3;
+   nrot[0] = (mm->input.lighting[v_off + ew_off]) & 3;
+   nrot[1] = (mm->input.lighting[v_off + ns_off]) & 3;
+   nrot[2] = (mm->input.lighting[v_off - ew_off]) & 3;
+   nrot[3] = (mm->input.lighting[v_off - ns_off]) & 3;
+   nrot[4] = (mm->input.lighting[v_off +      1]) & 3;
+   nrot[5] = (mm->input.lighting[v_off -      1]) & 3;
+   #endif
 
-      mm->config_dirty = 0;
+   if (geo == STBVOX_GEOM_transp) {
+      // transparency has a special rule: if the blocktype is the same,
+      // and the faces are compatible, then can hide them; otherwise,
+      // force them on
+      // Note that this means we don't support any transparentshapes other
+      // than solid blocks, since detecting them is too complicated. If
+      // you wanted to do something like minecraft water, you probably
+      // should just do that with a separate renderer anyway. (We don't
+      // support transparency sorting so you need to use alpha test
+      // anyway)
+      int i;
+      for (i=0; i < 6; ++i)
+         if (nbt[i] != bt) {
+            nbt[i] = 0;
+            ngeo[i] = STBVOX_GEOM_empty;
+         } else
+            ngeo[i] = STBVOX_GEOM_solid;
+      geo = STBVOX_GEOM_solid;
    }
-}
 
-int stbvox_make_mesh(stbvox_mesh_maker *mm)
-{
-   int x,y;
-   stbvox_bring_up_to_date(mm);
-   mm->full = 0;
-   if (mm->cur_x > mm->x0 || mm->cur_y > mm->y0 || mm->cur_z > mm->z0) {
-      stbvox_make_mesh_for_column(mm, mm->cur_x, mm->cur_y, mm->cur_z);
-      if (mm->full)
-         return 0;
-      ++mm->cur_y;
-      while (mm->cur_y < mm->y1 && !mm->full) {
-         stbvox_make_mesh_for_column(mm, mm->cur_x, mm->cur_y, mm->z0);
-         if (mm->full)
-            return 0;
-         ++mm->cur_y;
-      }
-      ++mm->cur_x;
+   // now compute the face visibility
+   visible_base = stbvox_hasface[geo][rot];
+   // @TODO: assert(visible_base != 0); // we should have early-outted earlier in this case
+   visible_faces = 0;
+
+   // now, for every face that might be visible, check if neighbor hides it
+   if (visible_base & (1 << STBVOX_FACE_east)) {
+      int  type = stbvox_facetype[ geo   ][(STBVOX_FACE_east+ rot   )&3];
+      int ntype = stbvox_facetype[ngeo[0]][(STBVOX_FACE_west+nrot[0])&3];
+      visible_faces |= ((stbvox_face_visible[type]) >> (ntype + 5 - STBVOX_FACE_east)) & (1 << STBVOX_FACE_east);
    }
-   for (x=mm->cur_x; x < mm->x1; ++x) {
-      for (y=mm->y0; y < mm->y1; ++y) {
-         stbvox_make_mesh_for_column(mm, x, y, mm->z0);
-         if (mm->full) {
-            mm->cur_x = x;
-            mm->cur_y = y;
-            return 0;
-         }
-      }
+   if (visible_base & (1 << STBVOX_FACE_north)) {
+      int  type = stbvox_facetype[ geo   ][(STBVOX_FACE_north+ rot   )&3];
+      int ntype = stbvox_facetype[ngeo[1]][(STBVOX_FACE_south+nrot[1])&3];
+      visible_faces |= ((stbvox_face_visible[type]) >> (ntype + 5 - STBVOX_FACE_north)) & (1 << STBVOX_FACE_north);
+   }
+   if (visible_base & (1 << STBVOX_FACE_west)) {
+      int  type = stbvox_facetype[ geo   ][(STBVOX_FACE_west+ rot   )&3];
+      int ntype = stbvox_facetype[ngeo[2]][(STBVOX_FACE_east+nrot[2])&3];
+      visible_faces |= ((stbvox_face_visible[type]) >> (ntype + 5 - STBVOX_FACE_west)) & (1 << STBVOX_FACE_west);
+   }
+   if (visible_base & (1 << STBVOX_FACE_south)) {
+      int  type = stbvox_facetype[ geo   ][(STBVOX_FACE_south+ rot   )&3];
+      int ntype = stbvox_facetype[ngeo[3]][(STBVOX_FACE_north+nrot[3])&3];
+      visible_faces |= ((stbvox_face_visible[type]) >> (ntype + 5 - STBVOX_FACE_south)) & (1 << STBVOX_FACE_south);
+   }
+   if (visible_base & (1 << STBVOX_FACE_up)) {
+      int  type = stbvox_facetype[ geo   ][STBVOX_FACE_up];
+      int ntype = stbvox_facetype[ngeo[4]][STBVOX_FACE_down];
+      visible_faces |= ((stbvox_face_visible[type]) >> (ntype + 5 - STBVOX_FACE_up)) & (1 << STBVOX_FACE_up);
+   }
+   if (visible_base & (1 << STBVOX_FACE_down)) {
+      int  type = stbvox_facetype[ geo   ][STBVOX_FACE_down];
+      int ntype = stbvox_facetype[ngeo[5]][STBVOX_FACE_up];
+      visible_faces |= ((stbvox_face_visible[type]) >> (ntype + 5 - STBVOX_FACE_down)) & (1 << STBVOX_FACE_down);
    }
-   return 1;
-}
 
-void stbvox_init_mesh_maker(stbvox_mesh_maker *mm)
-{
-   memset(mm, 0, sizeof(*mm));
-   stbvox_build_default_palette();
+   if (geo == STBVOX_GEOM_force)
+      geo = STBVOX_GEOM_solid;
 
-   mm->config_dirty = 1;
-   mm->default_mesh = 0;
-}
+   assert((geo == STBVOX_GEOM_crossed_pair) ? (visible_faces == 15) : 1);
 
-int stbvox_get_buffer_count(stbvox_mesh_maker *mm)
-{
-   stbvox_bring_up_to_date(mm);
-   return mm->num_mesh_slots;
-}
+   // now we finally know for sure which faces are getting generated
+   if (visible_faces == 0)
+      return;
 
-int stbvox_get_buffer_size_per_quad(stbvox_mesh_maker *mm, int n)
-{
-   return mm->output_size[0][n];
-}
+   mesh = mm->default_mesh;
+   if (mm->input.selector)
+      mesh = mm->input.selector[v_off];
 
-void stbvox_reset_buffers(stbvox_mesh_maker *mm)
-{
-   int i;
-   for (i=0; i < STBVOX_MAX_MESHES*STBVOX_MAX_MESH_SLOTS; ++i) {
-      mm->output_cur[0][i] = 0;
-      mm->output_buffer[0][i] = 0;
-   }
-}
+   if (geo <= STBVOX_GEOM_ceil_slope_north_is_bottom) {
+      // this is the simple case, we can just use regular block gen with special vmesh calculated with vheight
+      stbvox_mesh_vertex basevert;
+      stbvox_mesh_vertex vmesh[6][4];
+      stbvox_rotate rotate = { 0,0,0,0 };
+      unsigned char simple_rot = rot;
+      int i;
+      // we only need to do this for the displayed faces, but it's easier
+      // to just do it up front; @OPTIMIZE check if it's faster to do it
+      // for visible faces only
+      for (i=0; i < 6*4; ++i) {
+         int vert = stbvox_vertex_selector[0][i];
+         vert = stbvox_rotate_vertex[vert][rot];
+         vmesh[0][i] = stbvox_vmesh_pre_vheight[0][i]
+                     + stbvox_geometry_vheight[geo][vert];
+      }
 
-void stbvox_set_buffer(stbvox_mesh_maker *mm, int mesh, int slot, void *buffer, size_t len)
-{
-   int i;
-   stbvox_bring_up_to_date(mm);
-   mm->output_buffer[mesh][slot] = (char *) buffer;
-   mm->output_cur   [mesh][slot] = (char *) buffer;
-   mm->output_len   [mesh][slot] = len;
-   mm->output_end   [mesh][slot] = (char *) buffer + len;
-   for (i=0; i < STBVOX_MAX_MESH_SLOTS; ++i) {
-      if (mm->output_buffer[mesh][i]) {
-         assert(mm->output_len[mesh][i] / mm->output_size[mesh][i] == mm->output_len[mesh][slot] / mm->output_size[mesh][slot]);
+      basevert = stbvox_vertex_encode(pos.x, pos.y, pos.z << STBVOX_CONFIG_PRECISION_Z, 0,0);
+      if (mm->input.selector) {
+         mesh = mm->input.selector[v_off];
       }
-   }
-}
 
-void stbvox_set_default_mesh(stbvox_mesh_maker *mm, int mesh)
-{
-   mm->default_mesh = mesh;
-}
+      // check if we're going off the end
+      if (mm->output_cur[mesh][0] + mm->output_size[mesh][0]*6 > mm->output_end[mesh][0]) {
+         mm->full = 1;
+         return;
+      }
 
-int stbvox_get_quad_count(stbvox_mesh_maker *mm, int mesh)
-{
-   return (mm->output_cur[mesh][0] - mm->output_buffer[mesh][0]) / mm->output_size[mesh][0];
-}
+      if (geo >= STBVOX_GEOM_floor_slope_north_is_top) {
+         if (visible_faces & (1 << STBVOX_FACE_up)) {
+            int normal = geo == STBVOX_GEOM_floor_slope_north_is_top ? stbvox_floor_slope_for_rot[simple_rot] : STBVOX_FACE_up;
+            rotate.facerot = simple_rot;
+            stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_up  , v_off, pos, basevert, vmesh[STBVOX_FACE_up], mesh, normal);
+         }
+         if (visible_faces & (1 << STBVOX_FACE_down)) {
+            int normal = geo == STBVOX_GEOM_ceil_slope_north_is_bottom ? stbvox_ceil_slope_for_rot[simple_rot] : STBVOX_FACE_down;
+            rotate.facerot = (-rotate.facerot) & 3;
+            stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_down, v_off, pos, basevert, vmesh[STBVOX_FACE_down], mesh, normal);
+         }
+      } else {
+         if (visible_faces & (1 << STBVOX_FACE_up)) {
+            rotate.facerot = simple_rot;
+            stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_up  , v_off, pos, basevert, vmesh[STBVOX_FACE_up], mesh, STBVOX_FACE_up);
+         }
+         if (visible_faces & (1 << STBVOX_FACE_down)) {
+            rotate.facerot = (-rotate.facerot) & 3;
+            stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_down, v_off, pos, basevert, vmesh[STBVOX_FACE_down], mesh, STBVOX_FACE_down);
+         }
+      }
 
-stbvox_input_description *stbvox_get_input_description(stbvox_mesh_maker *mm)
-{
-   return &mm->input;
-}
+      if (mm->input.rotate) {
+         unsigned char val = mm->input.rotate[v_off];
+         rotate.block   = (val >> 0) & 3;
+         rotate.overlay = (val >> 2) & 3;
+         //rotate.tex2    = (val >> 4) & 3;
+         rotate.ecolor  = (val >> 6) & 3;
+      } else {
+         rotate.block = rotate.overlay = rotate.ecolor = simple_rot;
+      }
 
-void stbvox_set_input_range(stbvox_mesh_maker *mm, int x0, int y0, int z0, int x1, int y1, int z1)
-{
-   mm->x0 = x0;
-   mm->y0 = y0;
-   mm->z0 = z0;
+      rotate.facerot = 0;
 
-   mm->x1 = x1;
-   mm->y1 = y1;
-   mm->z1 = z1;
-
-   mm->cur_x = x0;
-   mm->cur_y = y0;
-   mm->cur_z = z0;
+      if (visible_faces & (1 << STBVOX_FACE_north))
+         stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_north, v_off, pos, basevert, vmesh[STBVOX_FACE_north], mesh, STBVOX_FACE_north);
+      if (visible_faces & (1 << STBVOX_FACE_south))
+         stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_south, v_off, pos, basevert, vmesh[STBVOX_FACE_south], mesh, STBVOX_FACE_south);
+      if (visible_faces & (1 << STBVOX_FACE_east))
+         stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_east , v_off, pos, basevert, vmesh[STBVOX_FACE_east ], mesh, STBVOX_FACE_east);
+      if (visible_faces & (1 << STBVOX_FACE_west))
+         stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_west , v_off, pos, basevert, vmesh[STBVOX_FACE_west ], mesh, STBVOX_FACE_west);
+   }
+   if (geo >= STBVOX_GEOM_floor_vheight_03) {
+      // this case can also be generated with regular block gen with special vmesh,
+      // except:
+      //     if we want to generate middle diagonal for 'weird' blocks
+      //     it's more complicated to detect neighbor matchups
+      stbvox_mesh_vertex vmesh[6][4];
+      stbvox_mesh_vertex cube[8];
+      stbvox_mesh_vertex basevert;
+      stbvox_rotate rotate = { 0,0,0,0 };
+      unsigned char simple_rot = rot;
+      unsigned char ht[4];
+      int extreme;
 
-   // @TODO validate that this range is representable in this mode
-}
+      // extract the heights
+      if (mm->input.vheight) {
+         unsigned char v =  mm->input.vheight[v_off];
+         ht[0] = (v >> 0) & 3;
+         ht[1] = (v >> 2) & 3;
+         ht[2] = (v >> 4) & 3;
+         ht[3] = (v >> 6) & 3;
+      } else if (mm->input.block_vheight) {
+         unsigned char v = mm->input.block_vheight[bt];
+         unsigned char raw[4];
+         int i;
 
-void stbvox_get_transform(stbvox_mesh_maker *mm, float transform[3][3])
-{
-   // scale
-   transform[0][0] = 1.0;
-   transform[0][1] = 1.0;
-   #if STBVOX_CONFIG_PRECISION_Z==1
-   transform[0][2] = 0.5f;
-   #else
-   transform[0][2] = 1.0f;
-   #endif
-   // translation
-   transform[1][0] = (float) (mm->pos_x);
-   transform[1][1] = (float) (mm->pos_y);
-   transform[1][2] = (float) (mm->pos_z);
-   // texture coordinate projection translation
-   transform[2][0] = (float) (mm->pos_x & 255); // @TODO depends on max texture scale
-   transform[2][1] = (float) (mm->pos_y & 255);
-   transform[2][2] = (float) (mm->pos_z & 255);
-}
+         raw[0] = (v >> 0) & 3;
+         raw[1] = (v >> 2) & 3;
+         raw[2] = (v >> 4) & 3;
+         raw[3] = (v >> 6) & 3;
 
-void stbvox_get_bounds(stbvox_mesh_maker *mm, float bounds[2][3])
-{
-   bounds[0][0] = (float) (mm->pos_x + mm->x0);
-   bounds[0][1] = (float) (mm->pos_y + mm->y0);
-   bounds[0][2] = (float) (mm->pos_z + mm->z0);
-   bounds[1][0] = (float) (mm->pos_x + mm->x1);
-   bounds[1][1] = (float) (mm->pos_y + mm->y1);
-   bounds[1][2] = (float) (mm->pos_z + mm->z1);
-}
+         for (i=0; i < 4; ++i)
+            ht[i] = raw[stbvox_rotate_vertex[i][rot]];
+      } else {
+         assert(0);
+      }
 
-void stbvox_set_mesh_coordinates(stbvox_mesh_maker *mm, int x, int y, int z)
-{
-   mm->pos_x = x;
-   mm->pos_y = y;
-   mm->pos_z = z;
-}
+      // flag whether any sides go off the top of the block, which means
+      // our visible_faces test was wrong
+      extreme = (ht[0] == 3 || ht[1] == 3 || ht[2] == 3 || ht[3] == 3);
 
-void stbvox_set_input_stride(stbvox_mesh_maker *mm, int x_stride_in_bytes, int y_stride_in_bytes)
-{
-   int f,v;
-   mm->x_stride_in_bytes = x_stride_in_bytes;
-   mm->y_stride_in_bytes = y_stride_in_bytes;
-   for (f=0; f < 6; ++f) {
-      for (v=0; v < 4; ++v) {
-         mm->cube_vertex_offset[f][v]   =   stbvox_vertex_vector[f][v][0]    * mm->x_stride_in_bytes
-                                         +  stbvox_vertex_vector[f][v][1]    * mm->y_stride_in_bytes
-                                         +  stbvox_vertex_vector[f][v][2]                           ;
-         mm->vertex_gather_offset[f][v] =  (stbvox_vertex_vector[f][v][0]-1) * mm->x_stride_in_bytes
-                                         + (stbvox_vertex_vector[f][v][1]-1) * mm->y_stride_in_bytes
-                                         + (stbvox_vertex_vector[f][v][2]-1)                        ; 
+      if (geo >= STBVOX_GEOM_ceil_vheight_03) {
+         cube[0] = stbvox_vertex_encode(0,0,ht[0],0,0);
+         cube[1] = stbvox_vertex_encode(0,0,ht[1],0,0);
+         cube[2] = stbvox_vertex_encode(0,0,ht[2],0,0);
+         cube[3] = stbvox_vertex_encode(0,0,ht[3],0,0);
+         cube[4] = stbvox_vertex_encode(0,0,2,0,0);
+         cube[5] = stbvox_vertex_encode(0,0,2,0,0);
+         cube[6] = stbvox_vertex_encode(0,0,2,0,0);
+         cube[7] = stbvox_vertex_encode(0,0,2,0,0);
+      } else {
+         cube[0] = stbvox_vertex_encode(0,0,0,0,0);
+         cube[1] = stbvox_vertex_encode(0,0,0,0,0);
+         cube[2] = stbvox_vertex_encode(0,0,0,0,0);
+         cube[3] = stbvox_vertex_encode(0,0,0,0,0);
+         cube[4] = stbvox_vertex_encode(0,0,ht[0],0,0);
+         cube[5] = stbvox_vertex_encode(0,0,ht[1],0,0);
+         cube[6] = stbvox_vertex_encode(0,0,ht[2],0,0);
+         cube[7] = stbvox_vertex_encode(0,0,ht[3],0,0);
+      }
+      if (!mm->input.vheight && mm->input.block_vheight) {
+         // @TODO: support block vheight here, I've forgotten what needs to be done specially
       }
-   }
-}
 
-/////////////////////////////////////////////////////////////////////////////
-//
-//    tables
-//
+      // build vertex mesh
+      {
+         int i;
+         for (i=0; i < 6*4; ++i) {
+            int vert = stbvox_vertex_selector[0][i];
+            vmesh[0][i] = stbvox_vmesh_pre_vheight[0][i]
+                        + cube[vert];
+         }
+      }
 
-// get opposite-facing normal & texgen for opposite face, used to map up-facing vheight data to down-facing data
-static unsigned char stbvox_reverse_face[STBVF_count] =
-{
-   STBVF_w, STBVF_s, STBVF_e, STBVF_n, STBVF_d   , STBVF_u   , STBVF_wd, STBVF_wu,
-         0,       0,       0,       0, STBVF_sw_d, STBVF_sw_u, STBVF_sd, STBVF_su,
-         0,       0,       0,       0, STBVF_se_d, STBVF_se_u, STBVF_ed, STBVF_eu,
-         0,       0,       0,       0, STBVF_ne_d, STBVF_ne_d, STBVF_nd, STBVF_nu
-};
+      basevert = stbvox_vertex_encode(pos.x, pos.y, pos.z << STBVOX_CONFIG_PRECISION_Z, 0,0);
+      // check if we're going off the end
+      if (mm->output_cur[mesh][0] + mm->output_size[mesh][0]*6 > mm->output_end[mesh][0]) {
+         mm->full = 1;
+         return;
+      }
 
-static float stbvox_default_texgen[2][32][3] =
-{
-   { {  0, 1,0 }, { 0, 0, 1 }, {  0,-1,0 }, { 0, 0,-1 },
-     { -1, 0,0 }, { 0, 0, 1 }, {  1, 0,0 }, { 0, 0,-1 },
-     {  0,-1,0 }, { 0, 0, 1 }, {  0, 1,0 }, { 0, 0,-1 },
-     {  1, 0,0 }, { 0, 0, 1 }, { -1, 0,0 }, { 0, 0,-1 },
+      // @TODO generate split faces
+      if (visible_faces & (1 << STBVOX_FACE_up)) {
+         if (geo >= STBVOX_GEOM_ceil_vheight_03)
+            // flat
+            stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_up  , v_off, pos, basevert, vmesh[STBVOX_FACE_up], mesh, STBVOX_FACE_up);
+         else {
+         #ifndef STBVOX_OPTIMIZED_VHEIGHT
+            // check if it's non-planar
+            if (cube[5] + cube[6] != cube[4] + cube[7]) {
+               // not planar, split along diagonal and make degenerate quads
+               if (geo == STBVOX_GEOM_floor_vheight_03)
+                  stbvox_make_03_split_mesh_for_face(mm, rotate, STBVOX_FACE_up, v_off, pos, basevert, vmesh[STBVOX_FACE_up], mesh, ht);
+               else
+                  stbvox_make_12_split_mesh_for_face(mm, rotate, STBVOX_FACE_up, v_off, pos, basevert, vmesh[STBVOX_FACE_up], mesh, ht);
+            } else
+               stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_up  , v_off, pos, basevert, vmesh[STBVOX_FACE_up], mesh, stbvox_planar_face_up_normal[ht[2]][ht[1]][ht[0]]);
+         #else
+            stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_up  , v_off, pos, basevert, vmesh[STBVOX_FACE_up], mesh, stbvox_optimized_face_up_normal[ht[3]][ht[2]][ht[1]][ht[0]]);
+         #endif
+         }
+      }
+      if (visible_faces & (1 << STBVOX_FACE_down)) {
+         if (geo < STBVOX_GEOM_ceil_vheight_03)
+            // flat
+            stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_down, v_off, pos, basevert, vmesh[STBVOX_FACE_down], mesh, STBVOX_FACE_down);
+         else {
+         #ifndef STBVOX_OPTIMIZED_VHEIGHT
+            // check if it's non-planar
+            if (cube[1] + cube[2] != cube[0] + cube[3]) {
+               // not planar, split along diagonal and make degenerate quads
+               if (geo == STBVOX_GEOM_ceil_vheight_03)
+                  stbvox_make_03_split_mesh_for_face(mm, rotate, STBVOX_FACE_down, v_off, pos, basevert, vmesh[STBVOX_FACE_down], mesh, ht);
+               else
+                  stbvox_make_12_split_mesh_for_face(mm, rotate, STBVOX_FACE_down, v_off, pos, basevert, vmesh[STBVOX_FACE_down], mesh, ht);
+            } else
+               stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_down, v_off, pos, basevert, vmesh[STBVOX_FACE_down], mesh, stbvox_reverse_face[stbvox_planar_face_up_normal[ht[2]][ht[1]][ht[0]]]);
+         #else
+            stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_down, v_off, pos, basevert, vmesh[STBVOX_FACE_down], mesh, stbvox_reverse_face[stbvox_optimized_face_up_normal[ht[3]][ht[2]][ht[1]][ht[0]]]);
+         #endif
+         }
+      }
 
-     {  1, 0,0 }, { 0, 1, 0 }, { -1, 0,0 }, { 0,-1, 0 },
-     { -1, 0,0 }, { 0,-1, 0 }, {  1, 0,0 }, { 0, 1, 0 },
-     {  1, 0,0 }, { 0, 1, 0 }, { -1, 0,0 }, { 0,-1, 0 },
-     { -1, 0,0 }, { 0,-1, 0 }, {  1, 0,0 }, { 0, 1, 0 },
-   },
-   { { 0, 0,-1 }, {  0, 1,0 }, { 0, 0, 1 }, {  0,-1,0 },
-     { 0, 0,-1 }, { -1, 0,0 }, { 0, 0, 1 }, {  1, 0,0 },
-     { 0, 0,-1 }, {  0,-1,0 }, { 0, 0, 1 }, {  0, 1,0 },
-     { 0, 0,-1 }, {  1, 0,0 }, { 0, 0, 1 }, { -1, 0,0 },
+      if (mm->input.rotate) {
+         unsigned char val = mm->input.rotate[v_off];
+         rotate.block   = (val >> 0) & 3;
+         rotate.overlay = (val >> 2) & 3;
+         //rotate.tex2    = (val >> 4) & 3;
+         rotate.ecolor  = (val >> 6) & 3;
+      } else if (mm->input.selector) {
+         rotate.block = rotate.overlay = rotate.ecolor = simple_rot;
+      }
 
-     { 0,-1, 0 }, {  1, 0,0 }, { 0, 1, 0 }, { -1, 0,0 },
-     { 0, 1, 0 }, { -1, 0,0 }, { 0,-1, 0 }, {  1, 0,0 },
-     { 0,-1, 0 }, {  1, 0,0 }, { 0, 1, 0 }, { -1, 0,0 },
-     { 0, 1, 0 }, { -1, 0,0 }, { 0,-1, 0 }, {  1, 0,0 },
-   },
-};
+      if ((visible_faces & (1 << STBVOX_FACE_north)) || (extreme && (ht[2] == 3 || ht[3] == 3)))
+         stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_north, v_off, pos, basevert, vmesh[STBVOX_FACE_north], mesh, STBVOX_FACE_north);
+      if ((visible_faces & (1 << STBVOX_FACE_south)) || (extreme && (ht[0] == 3 || ht[1] == 3))) 
+         stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_south, v_off, pos, basevert, vmesh[STBVOX_FACE_south], mesh, STBVOX_FACE_south);
+      if ((visible_faces & (1 << STBVOX_FACE_east)) || (extreme && (ht[1] == 3 || ht[3] == 3)))
+         stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_east , v_off, pos, basevert, vmesh[STBVOX_FACE_east ], mesh, STBVOX_FACE_east);
+      if ((visible_faces & (1 << STBVOX_FACE_west)) || (extreme && (ht[0] == 3 || ht[2] == 3)))
+         stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_west , v_off, pos, basevert, vmesh[STBVOX_FACE_west ], mesh, STBVOX_FACE_west);
+   }
 
-#define STBVOX_RSQRT2   0.7071067811865f
-#define STBVOX_RSQRT3   0.5773502691896f
+   if (geo == STBVOX_GEOM_crossed_pair) {
+      // this can be generated with a special vmesh
+      stbvox_mesh_vertex basevert = stbvox_vertex_encode(pos.x, pos.y, pos.z << STBVOX_CONFIG_PRECISION_Z , 0,0);
+      unsigned char simple_rot=0;
+      stbvox_rotate rot = { 0,0,0,0 };
+      unsigned char mesh = mm->default_mesh;
+      if (mm->input.selector) {
+         mesh = mm->input.selector[v_off];
+         simple_rot = mesh >> 4;
+         mesh &= 15;
+      }
 
-static float stbvox_default_normals[32][3] =
-{
-   { 1,0,0 },  // east
-   { 0,1,0 },  // north
-   { -1,0,0 }, // west
-   { 0,-1,0 }, // south
-   { 0,0,1 },  // up
-   { 0,0,-1 }, // down
-   {  STBVOX_RSQRT2,0, STBVOX_RSQRT2 }, // east & up
-   {  STBVOX_RSQRT2,0, -STBVOX_RSQRT2 }, // east & down
+      // check if we're going off the end
+      if (mm->output_cur[mesh][0] + mm->output_size[mesh][0]*4 > mm->output_end[mesh][0]) {
+         mm->full = 1;
+         return;
+      }
 
-   {  STBVOX_RSQRT2,0, STBVOX_RSQRT2 }, // east & up
-   { 0, STBVOX_RSQRT2, STBVOX_RSQRT2 }, // north & up
-   { -STBVOX_RSQRT2,0, STBVOX_RSQRT2 }, // west & up
-   { 0,-STBVOX_RSQRT2, STBVOX_RSQRT2 }, // south & up
-   {  STBVOX_RSQRT3, STBVOX_RSQRT3, STBVOX_RSQRT3 }, // ne & up
-   {  STBVOX_RSQRT3, STBVOX_RSQRT3,-STBVOX_RSQRT3 }, // ne & down
-   { 0, STBVOX_RSQRT2, STBVOX_RSQRT2 }, // north & up
-   { 0, STBVOX_RSQRT2, -STBVOX_RSQRT2 }, // north & down
-
-   {  STBVOX_RSQRT2,0, -STBVOX_RSQRT2 }, // east & down
-   { 0, STBVOX_RSQRT2, -STBVOX_RSQRT2 }, // north & down
-   { -STBVOX_RSQRT2,0, -STBVOX_RSQRT2 }, // west & down
-   { 0,-STBVOX_RSQRT2, -STBVOX_RSQRT2 }, // south & down
-   { -STBVOX_RSQRT3, STBVOX_RSQRT3, STBVOX_RSQRT3 }, // NW & up
-   { -STBVOX_RSQRT3, STBVOX_RSQRT3,-STBVOX_RSQRT3 }, // NW & down
-   { -STBVOX_RSQRT2,0, STBVOX_RSQRT2 }, // west & up
-   { -STBVOX_RSQRT2,0, -STBVOX_RSQRT2 }, // west & down
-
-   {  STBVOX_RSQRT3, STBVOX_RSQRT3,STBVOX_RSQRT3 }, // NE & up crossed
-   { -STBVOX_RSQRT3, STBVOX_RSQRT3,STBVOX_RSQRT3 }, // NW & up crossed
-   { -STBVOX_RSQRT3,-STBVOX_RSQRT3,STBVOX_RSQRT3 }, // SW & up crossed
-   {  STBVOX_RSQRT3,-STBVOX_RSQRT3,STBVOX_RSQRT3 }, // SE & up crossed
-   { -STBVOX_RSQRT3,-STBVOX_RSQRT3, STBVOX_RSQRT3 }, // SW & up
-   { -STBVOX_RSQRT3,-STBVOX_RSQRT3,-STBVOX_RSQRT3 }, // SW & up
-   { 0,-STBVOX_RSQRT2, STBVOX_RSQRT2 }, // south & up
-   { 0,-STBVOX_RSQRT2, -STBVOX_RSQRT2 }, // south & down
-};
-
-static float stbvox_default_texscale[128][4] =
-{
-   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
-   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
-   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
-   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
-   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
-   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
-   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
-   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
-   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
-   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
-   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
-   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
-   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
-   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
-   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
-   {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},
-};
-
-static unsigned char stbvox_default_palette_compact[64][3] =
-{
-   { 255,255,255 }, { 238,238,238 }, { 221,221,221 }, { 204,204,204 },
-   { 187,187,187 }, { 170,170,170 }, { 153,153,153 }, { 136,136,136 },
-   { 119,119,119 }, { 102,102,102 }, {  85, 85, 85 }, {  68, 68, 68 },
-   {  51, 51, 51 }, {  34, 34, 34 }, {  17, 17, 17 }, {   0,  0,  0 },
-   { 255,240,240 }, { 255,220,220 }, { 255,160,160 }, { 255, 32, 32 },
-   { 200,120,160 }, { 200, 60,150 }, { 220,100,130 }, { 255,  0,128 },
-   { 240,240,255 }, { 220,220,255 }, { 160,160,255 }, {  32, 32,255 },
-   { 120,160,200 }, {  60,150,200 }, { 100,130,220 }, {   0,128,255 },
-   { 240,255,240 }, { 220,255,220 }, { 160,255,160 }, {  32,255, 32 },
-   { 160,200,120 }, { 150,200, 60 }, { 130,220,100 }, { 128,255,  0 },
-   { 255,255,240 }, { 255,255,220 }, { 220,220,180 }, { 255,255, 32 },
-   { 200,160,120 }, { 200,150, 60 }, { 220,130,100 }, { 255,128,  0 },
-   { 255,240,255 }, { 255,220,255 }, { 220,180,220 }, { 255, 32,255 },
-   { 160,120,200 }, { 150, 60,200 }, { 130,100,220 }, { 128,  0,255 },
-   { 240,255,255 }, { 220,255,255 }, { 180,220,220 }, {  32,255,255 },
-   { 120,200,160 }, {  60,200,150 }, { 100,220,130 }, {   0,255,128 },
-};
-
-static unsigned char stbvox_vertex_vector[6][4][3] =
-{
-   { { 1,0,1 }, { 1,1,1 }, { 1,1,0 }, { 1,0,0 } }, // east
-   { { 1,1,1 }, { 0,1,1 }, { 0,1,0 }, { 1,1,0 } }, // north
-   { { 0,1,1 }, { 0,0,1 }, { 0,0,0 }, { 0,1,0 } }, // west
-   { { 0,0,1 }, { 1,0,1 }, { 1,0,0 }, { 0,0,0 } }, // south
-   { { 0,1,1 }, { 1,1,1 }, { 1,0,1 }, { 0,0,1 } }, // up
-   { { 0,0,0 }, { 1,0,0 }, { 1,1,0 }, { 0,1,0 } }, // down
-};
-
-// stbvox_vertex_vector, but read coordinates as binary numbers, zyx
-static unsigned char stbvox_vertex_selector[6][4] =
-{
-   { 5,7,3,1 },
-   { 7,6,2,3 },
-   { 6,4,0,2 },
-   { 4,5,1,0 },
-   { 6,7,5,4 },
-   { 0,1,3,2 },
-};
-
-static stbvox_mesh_vertex stbvox_vmesh_delta_normal[6][4] =
-{
-   {  stbvox_vertex_encode(1,0,1,0,0) , 
-      stbvox_vertex_encode(1,1,1,0,0) ,
-      stbvox_vertex_encode(1,1,0,0,0) ,
-      stbvox_vertex_encode(1,0,0,0,0)  },
-   {  stbvox_vertex_encode(1,1,1,0,0) ,
-      stbvox_vertex_encode(0,1,1,0,0) ,
-      stbvox_vertex_encode(0,1,0,0,0) ,
-      stbvox_vertex_encode(1,1,0,0,0)  },
-   {  stbvox_vertex_encode(0,1,1,0,0) ,
-      stbvox_vertex_encode(0,0,1,0,0) ,
-      stbvox_vertex_encode(0,0,0,0,0) ,
-      stbvox_vertex_encode(0,1,0,0,0)  },
-   {  stbvox_vertex_encode(0,0,1,0,0) ,
-      stbvox_vertex_encode(1,0,1,0,0) ,
-      stbvox_vertex_encode(1,0,0,0,0) ,
-      stbvox_vertex_encode(0,0,0,0,0)  },
-   {  stbvox_vertex_encode(0,1,1,0,0) ,
-      stbvox_vertex_encode(1,1,1,0,0) ,
-      stbvox_vertex_encode(1,0,1,0,0) ,
-      stbvox_vertex_encode(0,0,1,0,0)  },
-   {  stbvox_vertex_encode(0,0,0,0,0) ,
-      stbvox_vertex_encode(1,0,0,0,0) ,
-      stbvox_vertex_encode(1,1,0,0,0) ,
-      stbvox_vertex_encode(0,1,0,0,0)  }
-};
-
-static stbvox_mesh_vertex stbvox_vmesh_pre_vheight[6][4] =
-{
-   {  stbvox_vertex_encode(1,0,0,0,0) , 
-      stbvox_vertex_encode(1,1,0,0,0) ,
-      stbvox_vertex_encode(1,1,0,0,0) ,
-      stbvox_vertex_encode(1,0,0,0,0)  },
-   {  stbvox_vertex_encode(1,1,0,0,0) ,
-      stbvox_vertex_encode(0,1,0,0,0) ,
-      stbvox_vertex_encode(0,1,0,0,0) ,
-      stbvox_vertex_encode(1,1,0,0,0)  },
-   {  stbvox_vertex_encode(0,1,0,0,0) ,
-      stbvox_vertex_encode(0,0,0,0,0) ,
-      stbvox_vertex_encode(0,0,0,0,0) ,
-      stbvox_vertex_encode(0,1,0,0,0)  },
-   {  stbvox_vertex_encode(0,0,0,0,0) ,
-      stbvox_vertex_encode(1,0,0,0,0) ,
-      stbvox_vertex_encode(1,0,0,0,0) ,
-      stbvox_vertex_encode(0,0,0,0,0)  },
-   {  stbvox_vertex_encode(0,1,0,0,0) ,
-      stbvox_vertex_encode(1,1,0,0,0) ,
-      stbvox_vertex_encode(1,0,0,0,0) ,
-      stbvox_vertex_encode(0,0,0,0,0)  },
-   {  stbvox_vertex_encode(0,0,0,0,0) ,
-      stbvox_vertex_encode(1,0,0,0,0) ,
-      stbvox_vertex_encode(1,1,0,0,0) ,
-      stbvox_vertex_encode(0,1,0,0,0)  }
-};
-
-static stbvox_mesh_vertex stbvox_vmesh_delta_half_z[6][4] =
-{
-   { stbvox_vertex_encode(1,0,2,0,0) , 
-     stbvox_vertex_encode(1,1,2,0,0) ,
-     stbvox_vertex_encode(1,1,0,0,0) ,
-     stbvox_vertex_encode(1,0,0,0,0)  },
-   { stbvox_vertex_encode(1,1,2,0,0) ,
-     stbvox_vertex_encode(0,1,2,0,0) ,
-     stbvox_vertex_encode(0,1,0,0,0) ,
-     stbvox_vertex_encode(1,1,0,0,0)  },
-   { stbvox_vertex_encode(0,1,2,0,0) ,
-     stbvox_vertex_encode(0,0,2,0,0) ,
-     stbvox_vertex_encode(0,0,0,0,0) ,
-     stbvox_vertex_encode(0,1,0,0,0)  },
-   { stbvox_vertex_encode(0,0,2,0,0) ,
-     stbvox_vertex_encode(1,0,2,0,0) ,
-     stbvox_vertex_encode(1,0,0,0,0) ,
-     stbvox_vertex_encode(0,0,0,0,0)  },
-   { stbvox_vertex_encode(0,1,2,0,0) ,
-     stbvox_vertex_encode(1,1,2,0,0) ,
-     stbvox_vertex_encode(1,0,2,0,0) ,
-     stbvox_vertex_encode(0,0,2,0,0)  },
-   { stbvox_vertex_encode(0,0,0,0,0) ,
-     stbvox_vertex_encode(1,0,0,0,0) ,
-     stbvox_vertex_encode(1,1,0,0,0) ,
-     stbvox_vertex_encode(0,1,0,0,0)  }
-};
-
-static stbvox_mesh_vertex stbvox_vmesh_crossed_pair[6][4] =
-{
-   { stbvox_vertex_encode(1,0,2,0,0) , 
-     stbvox_vertex_encode(0,1,2,0,0) ,
-     stbvox_vertex_encode(0,1,0,0,0) ,
-     stbvox_vertex_encode(1,0,0,0,0)  },
-   { stbvox_vertex_encode(1,1,2,0,0) ,
-     stbvox_vertex_encode(0,0,2,0,0) ,
-     stbvox_vertex_encode(0,0,0,0,0) ,
-     stbvox_vertex_encode(1,1,0,0,0)  },
-   { stbvox_vertex_encode(0,1,2,0,0) ,
-     stbvox_vertex_encode(1,0,2,0,0) ,
-     stbvox_vertex_encode(1,0,0,0,0) ,
-     stbvox_vertex_encode(0,1,0,0,0)  },
-   { stbvox_vertex_encode(0,0,2,0,0) ,
-     stbvox_vertex_encode(1,1,2,0,0) ,
-     stbvox_vertex_encode(1,1,0,0,0) ,
-     stbvox_vertex_encode(0,0,0,0,0)  },
-   // not used, so we leave it non-degenerate to make sure it doesn't get gen'd accidentally
-   { stbvox_vertex_encode(0,1,2,0,0) ,
-     stbvox_vertex_encode(1,1,2,0,0) ,
-     stbvox_vertex_encode(1,0,2,0,0) ,
-     stbvox_vertex_encode(0,0,2,0,0)  },
-   { stbvox_vertex_encode(0,0,0,0,0) ,
-     stbvox_vertex_encode(1,0,0,0,0) ,
-     stbvox_vertex_encode(1,1,0,0,0) ,
-     stbvox_vertex_encode(0,1,0,0,0)  }
-};
-
-
-// this is used to determine if a face is ever generated at all
-static unsigned char stbvox_hasface[STBVOX_MAX_GEOM][STBVOX_NUM_ROTATION] =
-{
-   { 0,0,0,0 }, // empty
-   { 0,0,0,0 }, // knockout
-   { 63,63,63,63 }, // solid
-   { 63,63,63,63 }, // transp
-   { 63,63,63,63 }, // slab
-   { 63,63,63,63 }, // slab
-   { 1|2|4|48, 8|1|2|48, 4|8|1|48, 2|4|8|48, }, // floor slopes
-   { 1|2|4|48, 8|1|2|48, 4|8|1|48, 2|4|8|48, }, // ceil slopes
-   { 47,47,47,47 }, // wall-projected diagonal with down face
-   { 31,31,31,31 }, // wall-projected diagonal with up face
-   { 63,63,63,63 }, // crossed-pair has special handling, but avoid early-out
-   { 63,63,63,63 }, // force
-   { 63,63,63,63 },
-   { 63,63,63,63 },
-   { 63,63,63,63 },
-   { 63,63,63,63 },
-};
-
-// this determines which face type above is visible on each side of the geometry
-static unsigned char stbvox_facetype[STBVOX_GEOM_count][6] =
-{
-   { 0, },  // STBVOX_GEOM_empty
-   { STBVOX_FT_solid, STBVOX_FT_solid, STBVOX_FT_solid, STBVOX_FT_solid, STBVOX_FT_solid, STBVOX_FT_solid }, // knockout
-   { STBVOX_FT_solid, STBVOX_FT_solid, STBVOX_FT_solid, STBVOX_FT_solid, STBVOX_FT_solid, STBVOX_FT_solid }, // solid
-   { STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force }, // transp
-
-   { STBVOX_FT_upper, STBVOX_FT_upper, STBVOX_FT_upper, STBVOX_FT_upper, STBVOX_FT_solid, STBVOX_FT_force },
-   { STBVOX_FT_lower, STBVOX_FT_lower, STBVOX_FT_lower, STBVOX_FT_lower, STBVOX_FT_force, STBVOX_FT_solid },
-   { STBVOX_FT_diag_123, STBVOX_FT_solid, STBVOX_FT_diag_023, STBVOX_FT_none, STBVOX_FT_force, STBVOX_FT_solid },
-   { STBVOX_FT_diag_012, STBVOX_FT_solid, STBVOX_FT_diag_013, STBVOX_FT_none, STBVOX_FT_solid, STBVOX_FT_force },
-
-   { STBVOX_FT_diag_123, STBVOX_FT_solid, STBVOX_FT_diag_023, STBVOX_FT_force, STBVOX_FT_none, STBVOX_FT_solid },
-   { STBVOX_FT_diag_012, STBVOX_FT_solid, STBVOX_FT_diag_013, STBVOX_FT_force, STBVOX_FT_solid, STBVOX_FT_none },
-   { STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force, 0,0 }, // crossed pair
-   { STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force }, // GEOM_force
-
-   { STBVOX_FT_partial,STBVOX_FT_partial,STBVOX_FT_partial,STBVOX_FT_partial, STBVOX_FT_force, STBVOX_FT_solid }, // floor vheight, all neighbors forced
-   { STBVOX_FT_partial,STBVOX_FT_partial,STBVOX_FT_partial,STBVOX_FT_partial, STBVOX_FT_force, STBVOX_FT_solid }, // floor vheight, all neighbors forced
-   { STBVOX_FT_partial,STBVOX_FT_partial,STBVOX_FT_partial,STBVOX_FT_partial, STBVOX_FT_solid, STBVOX_FT_force }, // ceil vheight, all neighbors forced
-   { STBVOX_FT_partial,STBVOX_FT_partial,STBVOX_FT_partial,STBVOX_FT_partial, STBVOX_FT_solid, STBVOX_FT_force }, // ceil vheight, all neighbors forced
-};
-
-// This table indicates what normal to use for the "up" face of a sloped geom
-// @TODO this could be done with math given the current arrangement of the enum, but let's not require it
-static unsigned char stbvox_floor_slope_for_rot[4] =
-{
-   STBVF_su,
-   STBVF_wu, // @TODO: why is this reversed from what it should be? this is a north-is-up face, so slope should be south&up
-   STBVF_nu,
-   STBVF_eu,
-};
-
-static unsigned char stbvox_ceil_slope_for_rot[4] =
-{
-   STBVF_sd,
-   STBVF_ed,
-   STBVF_nd,
-   STBVF_wd,
-};
+      if (mm->input.rotate) {
+         unsigned char val = mm->input.rotate[v_off];
+         rot.block   = (val >> 0) & 3;
+         rot.overlay = (val >> 2) & 3;
+         //rot.tex2    = (val >> 4) & 3;
+         rot.ecolor  = (val >> 6) & 3;
+      } else if (mm->input.selector) {
+         rot.block = rot.overlay = rot.ecolor = simple_rot;
+      }
+      rot.facerot = 0;
 
-// this table indicates whether, for each pair of types above, a face is visible.
-// each value indicates whether a given type is visible for all neighbor types
-static unsigned short stbvox_face_visible[STBVOX_FT_count] =
-{
-   // we encode the table by listing which cases cause *obscuration*, and bitwise inverting that
-   // table is pre-shifted by 5 to save a shift when it's accessed
-   (unsigned short) ((~0x07ff                                          )<<5),  // none is completely obscured by everything
-   (unsigned short) ((~((1<<STBVOX_FT_solid) | (1<<STBVOX_FT_upper)   ))<<5),  // upper
-   (unsigned short) ((~((1<<STBVOX_FT_solid) | (1<<STBVOX_FT_lower)   ))<<5),  // lower
-   (unsigned short) ((~((1<<STBVOX_FT_solid)                          ))<<5),  // solid is only completely obscured only by solid
-   (unsigned short) ((~((1<<STBVOX_FT_solid) | (1<<STBVOX_FT_diag_013)))<<5),  // diag012 matches diag013
-   (unsigned short) ((~((1<<STBVOX_FT_solid) | (1<<STBVOX_FT_diag_123)))<<5),  // diag023 matches diag123
-   (unsigned short) ((~((1<<STBVOX_FT_solid) | (1<<STBVOX_FT_diag_012)))<<5),  // diag013 matches diag012
-   (unsigned short) ((~((1<<STBVOX_FT_solid) | (1<<STBVOX_FT_diag_023)))<<5),  // diag123 matches diag023
-   (unsigned short) ((~0                                               )<<5),  // force is always rendered regardless, always forces neighbor
-   (unsigned short) ((~((1<<STBVOX_FT_solid)                          ))<<5),  // partial is only completely obscured only by solid
-};
+      stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_north, v_off, pos, basevert, stbvox_vmesh_crossed_pair[STBVOX_FACE_north], mesh, STBVF_ne_u_cross);
+      stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_south, v_off, pos, basevert, stbvox_vmesh_crossed_pair[STBVOX_FACE_south], mesh, STBVF_sw_u_cross);
+      stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_east , v_off, pos, basevert, stbvox_vmesh_crossed_pair[STBVOX_FACE_east ], mesh, STBVF_se_u_cross);
+      stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_west , v_off, pos, basevert, stbvox_vmesh_crossed_pair[STBVOX_FACE_west ], mesh, STBVF_nw_u_cross);
+   }
 
-// the vertex heights of the block types, in binary vertex order (zyx):
-// lower: SW, SE, NW, NE; upper: SW, SE, NW, NE
-static stbvox_mesh_vertex stbvox_geometry_vheight[8][8] =
-{
-   #define STBVOX_HEIGHTS(a,b,c,d,e,f,g,h) \
-     { stbvox_vertex_encode(0,0,a,0,0),  \
-       stbvox_vertex_encode(0,0,b,0,0),  \
-       stbvox_vertex_encode(0,0,c,0,0),  \
-       stbvox_vertex_encode(0,0,d,0,0),  \
-       stbvox_vertex_encode(0,0,e,0,0),  \
-       stbvox_vertex_encode(0,0,f,0,0),  \
-       stbvox_vertex_encode(0,0,g,0,0),  \
-       stbvox_vertex_encode(0,0,h,0,0) }
 
-   STBVOX_HEIGHTS(0,0,0,0, 2,2,2,2),
-   STBVOX_HEIGHTS(0,0,0,0, 2,2,2,2),
-   STBVOX_HEIGHTS(0,0,0,0, 2,2,2,2),
-   STBVOX_HEIGHTS(0,0,0,0, 2,2,2,2),
-   STBVOX_HEIGHTS(1,1,1,1, 2,2,2,2),
-   STBVOX_HEIGHTS(0,0,0,0, 1,1,1,1),
-   STBVOX_HEIGHTS(0,0,0,0, 0,0,2,2),
-   STBVOX_HEIGHTS(2,2,0,0, 2,2,2,2),
-};
+   // @TODO
+   // STBVOX_GEOM_floor_slope_north_is_top_as_wall,
+   // STBVOX_GEOM_ceil_slope_north_is_bottom_as_wall,
+}
 
-// rotate vertices defined as [z][y][x] coords
-static unsigned char stbvox_rotate_vertex[8][4] =
+static void stbvox_make_mesh_for_column(stbvox_mesh_maker *mm, int x, int y, int z0)
 {
-   { 0,1,3,2 }, // zyx=000
-   { 1,3,2,0 }, // zyx=001
-   { 2,0,1,3 }, // zyx=010
-   { 3,2,0,1 }, // zyx=011
-   { 4,5,7,6 }, // zyx=100
-   { 5,7,6,4 }, // zyx=101
-   { 6,4,5,7 }, // zyx=110
-   { 7,6,4,5 }, // zyx=111
-};
+   stbvox_pos pos = { x,y,0 };
+   int v_off = x * mm->x_stride_in_bytes + y * mm->y_stride_in_bytes;
+   int ns_off = mm->y_stride_in_bytes;
+   int ew_off = mm->x_stride_in_bytes;
+   if (mm->input.geometry) {
+      unsigned char *bt  = mm->input.blocktype + v_off;
+      unsigned char *geo = mm->input.geometry + v_off;
+      int z;
+      for (z=z0; z < mm->z1; ++z) {
+         if (bt[z] && ( !bt[z+ns_off] || !STBVOX_GET_GEO(geo[z+ns_off]) || !bt[z-ns_off] || !STBVOX_GET_GEO(geo[z-ns_off])
+                      || !bt[z+ew_off] || !STBVOX_GET_GEO(geo[z+ew_off]) || !bt[z-ew_off] || !STBVOX_GET_GEO(geo[z-ew_off])))
+         {  // TODO check up and down
+            pos.z = z;
+            stbvox_make_mesh_for_block_with_geo(mm, pos, v_off+z);
+            if (mm->full) {
+               mm->cur_z = z;
+               return;
+            }
+         }
+      }
+   } else if (mm->input.block_geometry) {
+      int z;
+      unsigned char *bt  = mm->input.blocktype + v_off;
+      unsigned char *geo = mm->input.block_geometry;
+      for (z=z0; z < mm->z1; ++z) {
+         if (bt[z] && (    geo[bt[z+ns_off]] != STBVOX_GEOM_solid
+                        || geo[bt[z-ns_off]] != STBVOX_GEOM_solid
+                        || geo[bt[z+ew_off]] != STBVOX_GEOM_solid
+                        || geo[bt[z-ew_off]] != STBVOX_GEOM_solid
+                        || geo[bt[z-1]] != STBVOX_GEOM_solid
+                        || geo[bt[z+1]] != STBVOX_GEOM_solid))
+         {
+            pos.z = z;
+            stbvox_make_mesh_for_block_with_geo(mm, pos, v_off+z);
+            if (mm->full) {
+               mm->cur_z = z;
+               return;
+            }
+         }
+      }
+   } else {
+      unsigned char *bt = mm->input.blocktype + v_off;
+      int z;
+      #if STBVOX_CONFIG_PRECISION_Z == 1
+      stbvox_mesh_vertex *vmesh = stbvox_vmesh_delta_half_z[0];
+      #else
+      stbvox_mesh_vertex *vmesh = stbvox_vmesh_delta_normal[0];
+      #endif
+      for (z=z0; z < mm->z1; ++z) {
+         // if it's solid and at least one neighbor isn't solid
+         if (bt[z] && (!bt[z+ns_off] || !bt[z-ns_off] || !bt[z+ew_off] || !bt[z-ew_off] || !bt[z-1] || !bt[z+1])) {
+            pos.z = z;
+            stbvox_make_mesh_for_block(mm, pos, v_off+z, vmesh);
+            if (mm->full) {
+               mm->cur_z = z;
+               return;
+            }
+         }
+      }
+   }
+}
 
-#ifdef STBVOX_OPTIMIZED_VHEIGHT
-// optimized vheight generates a single normal over the entire face, even if it's not planar
-static stbvox_optimized_face_up_normal[4][4][4][4] =
+static void stbvox_bring_up_to_date(stbvox_mesh_maker *mm)
 {
-   {
-      {
-         { STBVF_u   , STBVF_ne_u, STBVF_ne_u, STBVF_ne_u, },
-         { STBVF_nw_u, STBVF_nu  , STBVF_nu  , STBVF_ne_u, },
-         { STBVF_nw_u, STBVF_nu  , STBVF_nu  , STBVF_nu  , },
-         { STBVF_nw_u, STBVF_nw_u, STBVF_nu  , STBVF_nu  , },
-      },{
-         { STBVF_su  , STBVF_eu  , STBVF_eu  , STBVF_ne_u, },
-         { STBVF_u   , STBVF_ne_u, STBVF_ne_u, STBVF_ne_u, },
-         { STBVF_nw_u, STBVF_nu  , STBVF_nu  , STBVF_ne_u, },
-         { STBVF_nw_u, STBVF_nu  , STBVF_nu  , STBVF_nu  , },
-      },{
-         { STBVF_eu  , STBVF_eu  , STBVF_eu  , STBVF_eu  , },
-         { STBVF_su  , STBVF_eu  , STBVF_eu  , STBVF_ne_u, },
-         { STBVF_u   , STBVF_ne_u, STBVF_ne_u, STBVF_ne_u, },
-         { STBVF_nw_u, STBVF_nu  , STBVF_nu  , STBVF_ne_u, },
-      },{
-         { STBVF_eu  , STBVF_eu  , STBVF_eu  , STBVF_eu  , },
-         { STBVF_eu  , STBVF_eu  , STBVF_eu  , STBVF_eu  , },
-         { STBVF_su  , STBVF_eu  , STBVF_eu  , STBVF_ne_u, },
-         { STBVF_u   , STBVF_ne_u, STBVF_ne_u, STBVF_ne_u, },
-      },
-   },{
-      {
-         { STBVF_sw_u, STBVF_u   , STBVF_ne_u, STBVF_ne_u, },
-         { STBVF_wu  , STBVF_nw_u, STBVF_nu  , STBVF_nu  , },
-         { STBVF_wu  , STBVF_nw_u, STBVF_nu  , STBVF_nu  , },
-         { STBVF_nw_u, STBVF_nw_u, STBVF_nw_u, STBVF_nu  , },
-      },{
-         { STBVF_su  , STBVF_su  , STBVF_eu  , STBVF_eu  , },
-         { STBVF_sw_u, STBVF_u   , STBVF_ne_u, STBVF_ne_u, },
-         { STBVF_wu  , STBVF_nw_u, STBVF_nu  , STBVF_nu  , },
-         { STBVF_wu  , STBVF_nw_u, STBVF_nu  , STBVF_nu  , },
-      },{
-         { STBVF_su  , STBVF_eu  , STBVF_eu  , STBVF_eu  , },
-         { STBVF_su  , STBVF_su  , STBVF_eu  , STBVF_eu  , },
-         { STBVF_sw_u, STBVF_u   , STBVF_ne_u, STBVF_ne_u, },
-         { STBVF_wu  , STBVF_nw_u, STBVF_nu  , STBVF_nu  , },
-      },{
-         { STBVF_su  , STBVF_eu  , STBVF_eu  , STBVF_eu  , },
-         { STBVF_su  , STBVF_eu  , STBVF_eu  , STBVF_eu  , },
-         { STBVF_su  , STBVF_su  , STBVF_eu  , STBVF_eu  , },
-         { STBVF_sw_u, STBVF_u   , STBVF_ne_u, STBVF_ne_u, },
-      },
-   },{
-      {
-         { STBVF_sw_u, STBVF_sw_u, STBVF_u   , STBVF_ne_u, },
-         { STBVF_wu  , STBVF_wu  , STBVF_nw_u, STBVF_nu  , },
-         { STBVF_wu  , STBVF_wu  , STBVF_nw_u, STBVF_nu  , },
-         { STBVF_wu  , STBVF_nw_u, STBVF_nw_u, STBVF_nw_u, },
-      },{
-         { STBVF_su  , STBVF_su  , STBVF_su  , STBVF_eu  , },
-         { STBVF_sw_u, STBVF_sw_u, STBVF_u   , STBVF_ne_u, },
-         { STBVF_wu  , STBVF_wu  , STBVF_nw_u, STBVF_nu  , },
-         { STBVF_wu  , STBVF_wu  , STBVF_nw_u, STBVF_nu  , },
-      },{
-         { STBVF_su  , STBVF_su  , STBVF_eu  , STBVF_eu  , },
-         { STBVF_su  , STBVF_su  , STBVF_su  , STBVF_eu  , },
-         { STBVF_sw_u, STBVF_sw_u, STBVF_u   , STBVF_ne_u, },
-         { STBVF_wu  , STBVF_wu  , STBVF_nw_u, STBVF_nu  , },
-      },{
-         { STBVF_su  , STBVF_su  , STBVF_eu  , STBVF_eu  , },
-         { STBVF_su  , STBVF_su  , STBVF_eu  , STBVF_eu  , },
-         { STBVF_su  , STBVF_su  , STBVF_su  , STBVF_eu  , },
-         { STBVF_sw_u, STBVF_sw_u, STBVF_u   , STBVF_ne_u, },
-      },
-   },{
-      {
-         { STBVF_sw_u, STBVF_sw_u, STBVF_sw_u, STBVF_u   , },
-         { STBVF_sw_u, STBVF_wu  , STBVF_wu  , STBVF_nw_u, },
-         { STBVF_wu  , STBVF_wu  , STBVF_wu  , STBVF_nw_u, },
-         { STBVF_wu  , STBVF_wu  , STBVF_nw_u, STBVF_nw_u, },
-      },{
-         { STBVF_sw_u, STBVF_su  , STBVF_su  , STBVF_su  , },
-         { STBVF_sw_u, STBVF_sw_u, STBVF_sw_u, STBVF_u   , },
-         { STBVF_sw_u, STBVF_wu  , STBVF_wu  , STBVF_nw_u, },
-         { STBVF_wu  , STBVF_wu  , STBVF_wu  , STBVF_nw_u, },
-      },{
-         { STBVF_su  , STBVF_su  , STBVF_su  , STBVF_eu  , },
-         { STBVF_sw_u, STBVF_su  , STBVF_su  , STBVF_su  , },
-         { STBVF_sw_u, STBVF_sw_u, STBVF_sw_u, STBVF_u   , },
-         { STBVF_sw_u, STBVF_wu  , STBVF_wu  , STBVF_nw_u, },
-      },{
-         { STBVF_su  , STBVF_su  , STBVF_su  , STBVF_eu  , },
-         { STBVF_su  , STBVF_su  , STBVF_su  , STBVF_eu  , },
-         { STBVF_sw_u, STBVF_su  , STBVF_su  , STBVF_su  , },
-         { STBVF_sw_u, STBVF_sw_u, STBVF_sw_u, STBVF_u   , },
-      },
-   },
-};
-#else
-// which normal to use for a given vheight that's planar
-// @TODO: this table was constructed by hand and may have bugs
-//                                 nw se sw
-static stbvox_planar_face_up_normal[4][4][4] =
-{   
-   {                                                      // sw,se,nw,ne;  ne = se+nw-sw
-      { STBVF_u   , 0         , 0         , 0          }, //  0,0,0,0; 1,0,0,-1; 2,0,0,-2; 3,0,0,-3;
-      { STBVF_u   , STBVF_u   , 0         , 0          }, //  0,1,0,1; 1,1,0, 0; 2,1,0,-1; 3,1,0,-2;
-      { STBVF_wu  , STBVF_nw_u, STBVF_nu  , 0          }, //  0,2,0,2; 1,2,0, 1; 2,2,0, 0; 3,2,0,-1;
-      { STBVF_wu  , STBVF_nw_u, STBVF_nw_u, STBVF_nu   }, //  0,3,0,3; 1,3,0, 2; 2,3,0, 1; 3,3,0, 0;
-   },{
-      { STBVF_u   , STBVF_u   , 0         , 0          }, //  0,0,1,1; 1,0,1, 0; 2,0,1,-1; 3,0,1,-2;
-      { STBVF_sw_u, STBVF_u   , STBVF_ne_u, 0          }, //  0,1,1,2; 1,1,1, 1; 2,1,1, 0; 3,1,1,-1;
-      { STBVF_sw_u, STBVF_u   , STBVF_u   , STBVF_ne_u }, //  0,2,1,3; 1,2,1, 2; 2,2,1, 1; 3,2,1, 0;
-      { 0         , STBVF_wu  , STBVF_nw_u, STBVF_nu   }, //  0,3,1,4; 1,3,1, 3; 2,3,1, 2; 3,3,1, 1;
-   },{
-      { STBVF_su  , STBVF_se_u, STBVF_eu  , 0          }, //  0,0,2,2; 1,0,2, 1; 2,0,2, 0; 3,0,2,-1;
-      { STBVF_sw_u, STBVF_u   , STBVF_u   , STBVF_ne_u }, //  0,1,2,3; 1,1,2, 2; 2,1,2, 1; 3,1,2, 0;
-      { 0         , STBVF_sw_u, STBVF_u   , STBVF_ne_u }, //  0,2,2,4; 1,2,2, 3; 2,2,2, 2; 3,2,2, 1;
-      { 0         , 0         , STBVF_u   , STBVF_u    }, //  0,3,2,5; 1,3,2, 4; 2,3,2, 3; 3,3,2, 2;
-   },{
-      { STBVF_su  , STBVF_se_u, STBVF_se_u, STBVF_eu   }, //  0,0,3,3; 1,0,3, 2; 2,0,3, 1; 3,0,3, 0;
-      { 0         , STBVF_su  , STBVF_se_u, STBVF_eu   }, //  0,1,3,4; 1,1,3, 3; 2,1,3, 2; 3,1,3, 1;
-      { 0         , 0         , STBVF_u   , STBVF_u    }, //  0,2,3,5; 1,2,3, 4; 2,2,3, 3; 3,2,3, 2;
-      { 0         , 0         , 0         , STBVF_u    }, //  0,3,3,6; 1,3,3, 5; 2,3,3, 4; 3,3,3, 3;
+   if (mm->config_dirty) {
+      int i;
+      #ifdef STBVOX_ICONFIG_FACE_ATTRIBUTE
+         mm->num_mesh_slots = 1;
+         for (i=0; i < STBVOX_MAX_MESHES; ++i) {
+            mm->output_size[i][0] = 32;
+            mm->output_step[i][0] = 8;
+         }
+      #else
+         mm->num_mesh_slots = 2;
+         for (i=0; i < STBVOX_MAX_MESHES; ++i) {
+            mm->output_size[i][0] = 16;
+            mm->output_step[i][0] = 4;
+            mm->output_size[i][1] = 4;
+            mm->output_step[i][1] = 4;
+         }
+      #endif
+
+      mm->config_dirty = 0;
    }
-};
+}
 
-// these tables were constructed automatically using a variant of the code
-// below; however, they seem wrong, so who knows
-static stbvox_face_up_normal_012[4][4][4] =
+int stbvox_make_mesh(stbvox_mesh_maker *mm)
 {
-   {
-      { STBVF_u   , STBVF_ne_u, STBVF_ne_u, STBVF_ne_u, },
-      { STBVF_wu  , STBVF_nu  , STBVF_ne_u, STBVF_ne_u, },
-      { STBVF_wu  , STBVF_nw_u, STBVF_nu  , STBVF_ne_u, },
-      { STBVF_wu  , STBVF_nw_u, STBVF_nw_u, STBVF_nu  , },
-   },{
-      { STBVF_su  , STBVF_eu  , STBVF_ne_u, STBVF_ne_u, },
-      { STBVF_sw_u, STBVF_u   , STBVF_ne_u, STBVF_ne_u, },
-      { STBVF_sw_u, STBVF_wu  , STBVF_nu  , STBVF_ne_u, },
-      { STBVF_sw_u, STBVF_wu  , STBVF_nw_u, STBVF_nu  , },
-   },{
-      { STBVF_su  , STBVF_eu  , STBVF_eu  , STBVF_ne_u, },
-      { STBVF_sw_u, STBVF_su  , STBVF_eu  , STBVF_ne_u, },
-      { STBVF_sw_u, STBVF_sw_u, STBVF_u   , STBVF_ne_u, },
-      { STBVF_sw_u, STBVF_sw_u, STBVF_wu  , STBVF_nu  , },
-   },{
-      { STBVF_su  , STBVF_su  , STBVF_eu  , STBVF_eu  , },
-      { STBVF_sw_u, STBVF_su  , STBVF_eu  , STBVF_eu  , },
-      { STBVF_sw_u, STBVF_sw_u, STBVF_su  , STBVF_eu  , },
-      { STBVF_sw_u, STBVF_sw_u, STBVF_sw_u, STBVF_u   , },
+   int x,y;
+   stbvox_bring_up_to_date(mm);
+   mm->full = 0;
+   if (mm->cur_x > mm->x0 || mm->cur_y > mm->y0 || mm->cur_z > mm->z0) {
+      stbvox_make_mesh_for_column(mm, mm->cur_x, mm->cur_y, mm->cur_z);
+      if (mm->full)
+         return 0;
+      ++mm->cur_y;
+      while (mm->cur_y < mm->y1 && !mm->full) {
+         stbvox_make_mesh_for_column(mm, mm->cur_x, mm->cur_y, mm->z0);
+         if (mm->full)
+            return 0;
+         ++mm->cur_y;
+      }
+      ++mm->cur_x;
    }
-};
+   for (x=mm->cur_x; x < mm->x1; ++x) {
+      for (y=mm->y0; y < mm->y1; ++y) {
+         stbvox_make_mesh_for_column(mm, x, y, mm->z0);
+         if (mm->full) {
+            mm->cur_x = x;
+            mm->cur_y = y;
+            return 0;
+         }
+      }
+   }
+   return 1;
+}
 
-static stbvox_face_up_normal_013[4][4][4] =
+void stbvox_init_mesh_maker(stbvox_mesh_maker *mm)
 {
-   {
-      { STBVF_u   , STBVF_eu  , STBVF_eu  , STBVF_eu  , },
-      { STBVF_nw_u, STBVF_nu  , STBVF_ne_u, STBVF_ne_u, },
-      { STBVF_nw_u, STBVF_nw_u, STBVF_nu  , STBVF_ne_u, },
-      { STBVF_nw_u, STBVF_nw_u, STBVF_nw_u, STBVF_nu  , },
-   },{
-      { STBVF_su  , STBVF_eu  , STBVF_eu  , STBVF_eu  , },
-      { STBVF_wu  , STBVF_u   , STBVF_eu  , STBVF_eu  , },
-      { STBVF_nw_u, STBVF_nw_u, STBVF_nu  , STBVF_ne_u, },
-      { STBVF_nw_u, STBVF_nw_u, STBVF_nw_u, STBVF_nu  , },
-   },{
-      { STBVF_su  , STBVF_su  , STBVF_su  , STBVF_eu  , },
-      { STBVF_sw_u, STBVF_su  , STBVF_eu  , STBVF_eu  , },
-      { STBVF_wu  , STBVF_wu  , STBVF_u   , STBVF_eu  , },
-      { STBVF_nw_u, STBVF_nw_u, STBVF_nw_u, STBVF_nu  , },
-   },{
-      { STBVF_su  , STBVF_su  , STBVF_su  , STBVF_eu  , },
-      { STBVF_sw_u, STBVF_su  , STBVF_su  , STBVF_su  , },
-      { STBVF_sw_u, STBVF_sw_u, STBVF_su  , STBVF_eu  , },
-      { STBVF_wu  , STBVF_wu  , STBVF_wu  , STBVF_u   , },
+   memset(mm, 0, sizeof(*mm));
+   stbvox_build_default_palette();
+
+   mm->config_dirty = 1;
+   mm->default_mesh = 0;
+}
+
+int stbvox_get_buffer_count(stbvox_mesh_maker *mm)
+{
+   stbvox_bring_up_to_date(mm);
+   return mm->num_mesh_slots;
+}
+
+int stbvox_get_buffer_size_per_quad(stbvox_mesh_maker *mm, int n)
+{
+   return mm->output_size[0][n];
+}
+
+void stbvox_reset_buffers(stbvox_mesh_maker *mm)
+{
+   int i;
+   for (i=0; i < STBVOX_MAX_MESHES*STBVOX_MAX_MESH_SLOTS; ++i) {
+      mm->output_cur[0][i] = 0;
+      mm->output_buffer[0][i] = 0;
    }
-};
+}
 
-static stbvox_face_up_normal_023[4][4][4] =
+void stbvox_set_buffer(stbvox_mesh_maker *mm, int mesh, int slot, void *buffer, size_t len)
 {
-   {
-      { STBVF_u   , STBVF_nu  , STBVF_nu  , STBVF_nu  , },
-      { STBVF_eu  , STBVF_eu  , STBVF_ne_u, STBVF_ne_u, },
-      { STBVF_su  , STBVF_eu  , STBVF_eu  , STBVF_ne_u, },
-      { STBVF_eu  , STBVF_eu  , STBVF_eu  , STBVF_eu  , },
-   },{
-      { STBVF_wu  , STBVF_nw_u, STBVF_nw_u, STBVF_nw_u, },
-      { STBVF_su  , STBVF_u   , STBVF_nu  , STBVF_nu  , },
-      { STBVF_su  , STBVF_eu  , STBVF_eu  , STBVF_ne_u, },
-      { STBVF_su  , STBVF_su  , STBVF_eu  , STBVF_eu  , },
-   },{
-      { STBVF_wu  , STBVF_nw_u, STBVF_nw_u, STBVF_nw_u, },
-      { STBVF_sw_u, STBVF_wu  , STBVF_nw_u, STBVF_nw_u, },
-      { STBVF_su  , STBVF_su  , STBVF_u   , STBVF_nu  , },
-      { STBVF_su  , STBVF_su  , STBVF_eu  , STBVF_eu  , },
-   },{
-      { STBVF_wu  , STBVF_nw_u, STBVF_nw_u, STBVF_nw_u, },
-      { STBVF_sw_u, STBVF_wu  , STBVF_nw_u, STBVF_nw_u, },
-      { STBVF_sw_u, STBVF_sw_u, STBVF_wu  , STBVF_nw_u, },
-      { STBVF_su  , STBVF_su  , STBVF_su  , STBVF_u   , },
+   int i;
+   stbvox_bring_up_to_date(mm);
+   mm->output_buffer[mesh][slot] = (char *) buffer;
+   mm->output_cur   [mesh][slot] = (char *) buffer;
+   mm->output_len   [mesh][slot] = len;
+   mm->output_end   [mesh][slot] = (char *) buffer + len;
+   for (i=0; i < STBVOX_MAX_MESH_SLOTS; ++i) {
+      if (mm->output_buffer[mesh][i]) {
+         assert(mm->output_len[mesh][i] / mm->output_size[mesh][i] == mm->output_len[mesh][slot] / mm->output_size[mesh][slot]);
+      }
    }
-};
+}
 
-static stbvox_face_up_normal_123[4][4][4] =
+void stbvox_set_default_mesh(stbvox_mesh_maker *mm, int mesh)
 {
-   {
-      { STBVF_u   , STBVF_nu  , STBVF_nu  , STBVF_nu  , },
-      { STBVF_eu  , STBVF_ne_u, STBVF_ne_u, STBVF_ne_u, },
-      { STBVF_eu  , STBVF_ne_u, STBVF_ne_u, STBVF_ne_u, },
-      { STBVF_eu  , STBVF_ne_u, STBVF_ne_u, STBVF_ne_u, },
-   },{
-      { STBVF_sw_u, STBVF_wu  , STBVF_nw_u, STBVF_nw_u, },
-      { STBVF_su  , STBVF_u   , STBVF_nu  , STBVF_nu  , },
-      { STBVF_eu  , STBVF_eu  , STBVF_ne_u, STBVF_ne_u, },
-      { STBVF_eu  , STBVF_eu  , STBVF_ne_u, STBVF_ne_u, },
-   },{
-      { STBVF_sw_u, STBVF_sw_u, STBVF_wu  , STBVF_nw_u, },
-      { STBVF_sw_u, STBVF_sw_u, STBVF_wu  , STBVF_nw_u, },
-      { STBVF_su  , STBVF_su  , STBVF_u   , STBVF_nu  , },
-      { STBVF_su  , STBVF_eu  , STBVF_eu  , STBVF_ne_u, },
-   },{
-      { STBVF_sw_u, STBVF_sw_u, STBVF_sw_u, STBVF_wu  , },
-      { STBVF_sw_u, STBVF_sw_u, STBVF_sw_u, STBVF_wu  , },
-      { STBVF_sw_u, STBVF_sw_u, STBVF_sw_u, STBVF_wu  , },
-      { STBVF_su  , STBVF_su  , STBVF_su  , STBVF_u   , },
+   mm->default_mesh = mesh;
+}
+
+int stbvox_get_quad_count(stbvox_mesh_maker *mm, int mesh)
+{
+   return (mm->output_cur[mesh][0] - mm->output_buffer[mesh][0]) / mm->output_size[mesh][0];
+}
+
+stbvox_input_description *stbvox_get_input_description(stbvox_mesh_maker *mm)
+{
+   return &mm->input;
+}
+
+void stbvox_set_input_range(stbvox_mesh_maker *mm, int x0, int y0, int z0, int x1, int y1, int z1)
+{
+   mm->x0 = x0;
+   mm->y0 = y0;
+   mm->z0 = z0;
+
+   mm->x1 = x1;
+   mm->y1 = y1;
+   mm->z1 = z1;
+
+   mm->cur_x = x0;
+   mm->cur_y = y0;
+   mm->cur_z = z0;
+
+   // @TODO validate that this range is representable in this mode
+}
+
+void stbvox_get_transform(stbvox_mesh_maker *mm, float transform[3][3])
+{
+   // scale
+   transform[0][0] = 1.0;
+   transform[0][1] = 1.0;
+   #if STBVOX_CONFIG_PRECISION_Z==1
+   transform[0][2] = 0.5f;
+   #else
+   transform[0][2] = 1.0f;
+   #endif
+   // translation
+   transform[1][0] = (float) (mm->pos_x);
+   transform[1][1] = (float) (mm->pos_y);
+   transform[1][2] = (float) (mm->pos_z);
+   // texture coordinate projection translation
+   transform[2][0] = (float) (mm->pos_x & 255); // @TODO depends on max texture scale
+   transform[2][1] = (float) (mm->pos_y & 255);
+   transform[2][2] = (float) (mm->pos_z & 255);
+}
+
+void stbvox_get_bounds(stbvox_mesh_maker *mm, float bounds[2][3])
+{
+   bounds[0][0] = (float) (mm->pos_x + mm->x0);
+   bounds[0][1] = (float) (mm->pos_y + mm->y0);
+   bounds[0][2] = (float) (mm->pos_z + mm->z0);
+   bounds[1][0] = (float) (mm->pos_x + mm->x1);
+   bounds[1][1] = (float) (mm->pos_y + mm->y1);
+   bounds[1][2] = (float) (mm->pos_z + mm->z1);
+}
+
+void stbvox_set_mesh_coordinates(stbvox_mesh_maker *mm, int x, int y, int z)
+{
+   mm->pos_x = x;
+   mm->pos_y = y;
+   mm->pos_z = z;
+}
+
+void stbvox_set_input_stride(stbvox_mesh_maker *mm, int x_stride_in_bytes, int y_stride_in_bytes)
+{
+   int f,v;
+   mm->x_stride_in_bytes = x_stride_in_bytes;
+   mm->y_stride_in_bytes = y_stride_in_bytes;
+   for (f=0; f < 6; ++f) {
+      for (v=0; v < 4; ++v) {
+         mm->cube_vertex_offset[f][v]   =   stbvox_vertex_vector[f][v][0]    * mm->x_stride_in_bytes
+                                         +  stbvox_vertex_vector[f][v][1]    * mm->y_stride_in_bytes
+                                         +  stbvox_vertex_vector[f][v][2]                           ;
+         mm->vertex_gather_offset[f][v] =  (stbvox_vertex_vector[f][v][0]-1) * mm->x_stride_in_bytes
+                                         + (stbvox_vertex_vector[f][v][1]-1) * mm->y_stride_in_bytes
+                                         + (stbvox_vertex_vector[f][v][2]-1)                        ; 
+      }
    }
-};
-#endif
+}
 
 /////////////////////////////////////////////////////////////////////////////
 //

+ 3 - 0
tests/test_cpp_compilation.cpp

@@ -7,6 +7,8 @@
 #define STB_IMAGE_IMPLEMENTATION
 #define STB_HERRINGBONE_WANG_TILE_IMPLEMENTATION
 #define STB_RECT_PACK_IMPLEMENTATION
+#define STB_VOXEL_RENDER_IMPLEMENTATION
+#define STBVOX_CONFIG_MODE 1
 
 #define STBI_MALLOC     my_malloc
 #define STBI_FREE       my_free
@@ -25,6 +27,7 @@ void my_free(void *) { }
 #include "stb_c_lexer.h"
 #include "stb_divide.h"
 #include "stb_herringbone_wang_tile.h"
+#include "stb_voxel_render.h"
 
 #define STBTE_DRAW_RECT(x0,y0,x1,y1,color)      do ; while(0)
 #define STBTE_DRAW_TILE(x,y,id,highlight,data)  do ; while(0)