bug fixes
[convexer.git] / src / convexer.c
index 7669e97e1ce3550e3f5f3764f10300785b73d01e..adcaf6007a2ae626a9b8d94b9bd9e52ce4523673 100644 (file)
@@ -31,6 +31,8 @@
        stb/          C        Sean Barrets image I/O
 */
 
+const char *cxr_build_time = __DATE__ " @" __TIME__;
+
 #include <stdio.h>
 #include <math.h>
 #include <stdint.h>
@@ -38,8 +40,6 @@
 #include <stdlib.h>
 #include <string.h>
 
-const char *cxr_build_time = __DATE__ " @" __TIME__;
-
 typedef uint8_t        u8;
 typedef uint16_t       u16;
 typedef uint32_t       u32;
@@ -59,6 +59,8 @@ typedef v3f                   m4x3f[4];
 typedef v3f                    boxf[2];
 
 #define CXR_EPSILON 0.001
+#define CXR_PLANE_SIMILARITY_MAX 0.999
+#define CXR_BIG_NUMBER 1e300
 #define CXR_INTERIOR_ANGLE_MAX 0.998
 #define CXR_API 
 #define CXR_DIRTY_OPTIMISATION 1
@@ -103,6 +105,7 @@ struct cxr_input_mesh
    struct cxr_edge
    {
       i32 i0, i1;
+      i32 freestyle;
    }
    *edges;
 
@@ -163,6 +166,7 @@ struct cxr_texinfo
 {
    v3f uaxis, vaxis;
    v2f offset, scale;
+   double winding;
 };
 
 // simple VDF writing interface
@@ -374,6 +378,32 @@ static void cxr_vdf_kaxis(struct cxr_vdf *vdf, const char *strk, v3f normal, dou
 {
    cxr_vdf_printf( vdf, "\"%s\" \"[%f %f %f %f] %f\"\n", strk, normal[0],normal[1],normal[2],offset,scale );
 }
+static void cxr_vdf_kv3f(struct cxr_vdf *vdf, const char *strk, v3f v)
+{
+   cxr_vdf_printf( vdf, "\"%s\" \"[%f %f %f]\"\n", strk, v[0], v[1], v[2] );
+}
+static void cxr_vdf_karrdouble(struct cxr_vdf *vdf, const char *strk, int id, double *doubles, int count)
+{
+   cxr_vdf_put(vdf,"");
+   fprintf( vdf->fp, "\"%s%d\" \"", strk, id );
+   for( int i=0; i<count; i++ )
+   {
+      if( i == count-1 ) fprintf( vdf->fp, "%f", doubles[i] );
+      else fprintf( vdf->fp, "%f ", doubles[i] );
+   }
+   fprintf( vdf->fp, "\"\n" );
+}
+static void cxr_vdf_karrv3f(struct cxr_vdf *vdf, const char *strk, int id, v3f *vecs, int count)
+{
+   cxr_vdf_put(vdf,"");
+   fprintf( vdf->fp, "\"%s%d\" \"", strk, id );
+   for( int i=0; i<count; i++ )
+   {
+      if( i == count-1 ) fprintf( vdf->fp, "%f %f %f", vecs[i][0], vecs[i][1], vecs[i][2] );
+      else fprintf( vdf->fp, "%f %f %f ", vecs[i][0], vecs[i][1], vecs[i][2] );
+   }
+   fprintf( vdf->fp, "\"\n" );
+}
 static void cxr_vdf_plane(struct cxr_vdf *vdf, const char *strk, v3f a, v3f b, v3f c )
 {
    cxr_vdf_printf( vdf, "\"%s\" \"(%f %f %f) (%f %f %f) (%f %f %f)\"\n",
@@ -473,8 +503,17 @@ static void cxr_mesh_clean_edges(struct cxr_mesh *mesh)
          struct cxr_edge edge = { i0, i1 };
          // --- ! ---
          // Copy extra information (sharp,freestyle.. etc) here!
-         //
-         // if orig_edge_id < mesh->edges.count: edge.foo = mesh->edges[orig].foo
+
+         if( orig_edge_id < mesh->edges.count )
+         {
+            struct cxr_edge *orig_edge = cxr_ab_ptr( &mesh->edges, orig_edge_id );
+            edge.freestyle = orig_edge->freestyle;
+         }
+         else
+         {
+            edge.freestyle = 0;
+         }
+
          // --- ! ---
          cxr_ab_push( &new_edges, &edge );
 
@@ -760,8 +799,8 @@ static struct cxr_mesh *cxr_pull_best_solid(
       for( int j=0; j<polya->loop_total; j++ )
       {
          struct cxr_loop *loop = cxr_ab_ptr(&mesh->loops, polya->loop_start+j);
-         if( plane_polarity( planeb, vertices[loop->index] ) > 0.001 ||
-             v3_dot(polya->normal,polyb->normal) > 0.98500 )
+         if( plane_polarity( planeb, vertices[loop->index] ) > 0.000025 ||
+             v3_dot(polya->normal,polyb->normal) > CXR_PLANE_SIMILARITY_MAX )
          {
             edge_tagged[i] = 1;
             break;
@@ -799,7 +838,7 @@ static struct cxr_mesh *cxr_pull_best_solid(
             struct cxr_polygon *polyj = cxr_ab_ptr(&mesh->polys,connected_planes[j]);
             struct cxr_polygon *polyk = cxr_ab_ptr(&mesh->polys,connected_planes[k]);
 
-            if( v3_dot(polyj->normal, polyk->normal) > 0.98500 )
+            if( v3_dot(polyj->normal, polyk->normal) > CXR_PLANE_SIMILARITY_MAX )
                goto IL_TAG_VERT;
          }
       }
@@ -958,7 +997,7 @@ IL_TAG_NEXT_VERT:;
                      for( int m=0; m<solid_len; m++ )
                      {
                         struct cxr_polygon *polym = cxr_ab_ptr(&mesh->polys,solid[m]);
-                        if( v3_dot( polym->normal, future_face->normal ) > 0.98500 )
+                        if( v3_dot( polym->normal,future_face->normal) > CXR_PLANE_SIMILARITY_MAX)
                            goto IL_SKIP_PLANE_ADD;
                      }
 
@@ -1571,12 +1610,13 @@ static void cxr_calculate_axis(
       v2_zero( transform->offset );
 
       v2_div( (v2f){128.0, 128.0}, texture_res, transform->scale );
+      transform->winding = 1.0;
       return;
    }
    
    // Detect if UV is reversed
    double winding = v2_cross( tT, bT ) >= 0.0f? 1.0f: -1.0f;
-
+   
    // UV projection reference
    v2f vY, vX;
    v2_muls((v2f){1,0}, winding, vX);
@@ -1641,6 +1681,7 @@ static void cxr_calculate_axis(
    v3_copy( vaxis, transform->vaxis );
    v2_copy( tex_offset, transform->offset );
    v2_copy( uv_scale, transform->scale );
+   transform->winding = winding;
 }
 
 CXR_API struct cxr_input_mesh *cxr_decompose(struct cxr_input_mesh *src)
@@ -1771,6 +1812,707 @@ CXR_API struct cxr_input_mesh *cxr_decompose(struct cxr_input_mesh *src)
    return NULL;
 }
 
+static int cxr_cardinal( v3f a, int ignore )
+{
+   int component = 0;
+   double component_max = -CXR_BIG_NUMBER;
+
+   for( int i=0; i<3; i++ )
+   {
+      if( i == ignore ) continue;
+
+      if( fabs(a[i]) > component_max )
+      {
+         component_max = fabs(a[i]);
+         component = i;
+      }
+   }
+   double d = a[component] >= 0.0? 1.0: -1.0;
+   v3_zero( a );
+   a[component] = d;
+
+   return component;
+}
+
+// Convert contiguous mesh to patch of displacments
+//
+static void cxr_write_disp(struct cxr_mesh *mesh, struct cxr_input_mesh *inputmesh, 
+   struct cxr_vdf *output, 
+   struct cxr_auto_buffer *abverts)
+{
+   // Create a graph which maps vertices by their connections
+   struct vertinfo
+   {
+      int con_start, con_count;     // Index into the connection graph
+      int boundary,
+          used,
+          search,
+          corner;
+
+      double alpha;
+   }
+   *vertinfo = malloc( sizeof(struct vertinfo)*abverts->count );
+   int *graph = malloc( sizeof(int) * mesh->edges.count*2 );
+   
+   int con_pos = 0;
+   for( int i=0; i<abverts->count; i++ )
+   {
+      struct vertinfo *info = &vertinfo[i];
+      info->con_start = con_pos;
+      info->con_count = 0;
+      info->boundary = 0;
+      info->corner = 0;
+      info->used = 0;
+      info->search = 0;
+      info->alpha = 0.0;
+
+      for( int j=0; j<mesh->edges.count; j++ )
+      {
+         struct cxr_edge *edge = cxr_ab_ptr(&mesh->edges,j);
+
+         if( edge->i0 == i || edge->i1 == i )
+         {
+            graph[ con_pos ++ ] = edge->i0 == i? edge->i1: edge->i0;
+            info->con_count ++;
+
+            if( edge->freestyle )
+               info->boundary = 1;
+         }
+      }
+   }
+
+   // Find best normal for brush patch. VBSP uses the original brush
+   // as reference for decal projection.
+   //
+   // These are clamped to be cardinal directions as to make the VMF somewhat
+   // human editable.
+
+   v3f avg_normal, refv, refu, refn;
+   v3_zero(refv); v3_zero(refu); v3_zero(refn);
+   
+   for( int i=0; i<mesh->polys.count; i++ )
+   {
+      struct cxr_polygon *poly = cxr_ab_ptr( &mesh->polys, i );
+      v3_add( poly->normal, avg_normal, avg_normal );
+   }
+   v3_divs( avg_normal, mesh->polys.count, avg_normal );
+   v3_normalize( avg_normal );   // TODO: This can be zero length. Should add a safety check
+                                 // normalize function that checks for small length before
+                                 // carrying out, otherwise we get inf/nan values...
+   int n_cardinal = cxr_cardinal( avg_normal, -1 );
+
+   // Approximately matching the area of the result brush faces to the actual area
+   // this is to assign a 'best guess' amount of lightmap texels.
+   //
+   double uv_area = 0.0, face_area = 0.0, sf;
+   v2f uvboundmin, uvboundmax;
+   v3f faceboundmin, faceboundmax;
+   v2f uv_center;
+   v3f face_center;
+
+   v2_fill( uvboundmin,  CXR_BIG_NUMBER );
+   v2_fill( uvboundmax, -CXR_BIG_NUMBER );
+   v3_fill( faceboundmin,  CXR_BIG_NUMBER );
+   v3_fill( faceboundmax, -CXR_BIG_NUMBER );
+
+   for( int i=0; i<mesh->polys.count; i++ )
+   {
+      struct cxr_polygon *poly = cxr_ab_ptr( &mesh->polys, i );
+      
+      for( int j=0; j<poly->loop_total; j++ )
+      {
+         struct cxr_loop *lp0 = cxr_ab_ptr(&mesh->loops, poly->loop_start+j);
+         v2_minv( lp0->uv, uvboundmin, uvboundmin);
+         v2_maxv( lp0->uv, uvboundmax, uvboundmax);
+         v3_minv( cxr_ab_ptr(abverts,lp0->index), faceboundmin, faceboundmin );
+         v3_maxv( cxr_ab_ptr(abverts,lp0->index), faceboundmax, faceboundmax );
+      }
+
+      for( int j=0; j<poly->loop_total-2; j++ )
+      {
+         struct cxr_loop *lp0 = cxr_ab_ptr(&mesh->loops, poly->loop_start),
+                         *lp1 = cxr_ab_ptr(&mesh->loops, poly->loop_start+j+1),
+                         *lp2 = cxr_ab_ptr(&mesh->loops, poly->loop_start+j+2);
+         v3f va, vb, orth;
+         v3_sub( cxr_ab_ptr(abverts,lp1->index), cxr_ab_ptr(abverts,lp0->index), va );
+         v3_sub( cxr_ab_ptr(abverts,lp2->index), cxr_ab_ptr(abverts,lp0->index), vb );
+         v3_cross( va, vb, orth );
+         
+         face_area += v3_length( orth ) / 2.0;
+         
+         v2f uva, uvb;
+         v2_sub( lp1->uv, lp0->uv, uva );
+         v2_sub( lp2->uv, lp0->uv, uvb );
+         
+         uv_area += fabs(v2_cross( uva, uvb )) / 2.0;
+      }
+   }
+
+   v3_add( faceboundmax, faceboundmin, face_center );
+   v3_muls( face_center, 0.5, face_center );
+   v2_add( uvboundmin, uvboundmax, uv_center );
+   v2_muls( uv_center, 0.5, uv_center );
+
+   sf = sqrt( face_area / uv_area );
+   int corner_count = 0;
+
+   // Vertex classification
+   for( int i=0; i<abverts->count; i++ )
+   {
+      struct vertinfo *info = &vertinfo[i];
+      if( !info->boundary ) continue;
+
+      int count = 0,
+          non_manifold = 1;
+
+      for( int j=0; j<info->con_count; j++ )
+      {
+         int con = graph[info->con_start+j];
+
+         if( vertinfo[con].boundary )
+            count ++;
+         else
+            non_manifold = 0;
+      }
+      if( count > 2 || non_manifold )
+      {
+         info->corner = 1;
+         corner_count ++;
+
+         //cxr_debug_box( cxr_ab_ptr(abverts,i), 0.1, colour_success );
+      }
+   }
+   
+   int dispedge[16];
+   v2f corner_uvs[4];
+   int dispedge_count;
+   int disp_count = 0;
+
+   for( int i=0; i<mesh->polys.count; i++ )
+   {
+      struct cxr_polygon *basepoly = cxr_ab_ptr(&mesh->polys,i);
+
+      for( int h=0; h<basepoly->loop_total; h ++ )
+      {
+         int i0 = h,
+             i1 = cxr_range(h+1,basepoly->loop_total);
+
+         struct cxr_loop *l0 = cxr_ab_ptr(&mesh->loops, basepoly->loop_start+i0),
+                         *l1 = cxr_ab_ptr(&mesh->loops, basepoly->loop_start+i1);
+         struct vertinfo *info = &vertinfo[ l0->index ];
+
+         if( info->corner )
+         { 
+            int corner_count = 1;
+            
+            struct cxr_material *matptr = 
+               basepoly->material_id < 0 || inputmesh->material_count == 0? 
+                  &cxr_nodraw:
+                  &inputmesh->materials[ basepoly->material_id ];
+
+            dispedge_count = 2;
+            dispedge[0] = l0->index;
+            dispedge[1] = l1->index;
+            v2_copy( l0->uv, corner_uvs[0] );
+            
+            // Consume (remove) faces we use for corners
+            basepoly->loop_total = -1;
+            
+            //cxr_debug_box( cxr_ab_ptr(abverts,l0->index),0.08,(v4f){0.0,0.0,1.0,1.0});
+
+            // Collect edges 
+            // --------------------
+
+            while( dispedge_count < 17 )
+            {
+               struct vertinfo *edge_head = &vertinfo[dispedge[dispedge_count-1]];
+               int newvert = 0;
+               
+               if( edge_head->corner )
+               {
+                  // Find a polygon that has the edge C-1 -> C
+                  for( int j=0; j<mesh->polys.count && !newvert; j++ )
+                  {
+                     struct cxr_polygon *poly = cxr_ab_ptr(&mesh->polys,j);
+
+                     for( int k=0; k<poly->loop_total; k ++ )
+                     {
+                        int i0 = k,
+                            i1 = cxr_range(k+1,poly->loop_total);
+
+                        struct cxr_loop *l0 = cxr_ab_ptr(&mesh->loops, poly->loop_start+i0),
+                                        *l1 = cxr_ab_ptr(&mesh->loops, poly->loop_start+i1);
+
+                        if( l0->index == dispedge[dispedge_count-2] &&
+                            l1->index == dispedge[dispedge_count-1] )
+                        {
+                           // Take the vertex after that edge
+                           v2_copy( l1->uv, corner_uvs[corner_count ++] );
+
+                           int i2 = cxr_range(i1+1,poly->loop_total);
+                           struct cxr_loop *l2 = cxr_ab_ptr(&mesh->loops, poly->loop_start+i2);
+
+                           dispedge[dispedge_count ++] = l2->index;
+                           newvert = 1;
+                           poly->loop_total = -1;
+                           break;
+                        }
+                     }
+                  }
+               }
+               else
+               {
+                  for( int j=0; j<edge_head->con_count; j++ )
+                  {
+                     int con = graph[edge_head->con_start+j];
+
+                     if( con == -1 )
+                        continue;
+
+                     if( dispedge_count > 1 )
+                        if( con == dispedge[dispedge_count-2] )
+                           continue;
+                     
+                     struct vertinfo *coninfo = &vertinfo[con];
+                     
+                     if( !coninfo->boundary )
+                        continue;
+                     
+                     /*
+                     cxr_debug_arrow( cxr_ab_ptr(abverts,dispedge[dispedge_count-1]),
+                                      cxr_ab_ptr(abverts,con),
+                                      (v3f){0,0,1},
+                                      0.1,
+                                      colour_success );
+                     */
+
+                     dispedge[ dispedge_count ++ ] = con;
+                     newvert = 1;
+
+                     break;
+                  }
+               }
+
+               if( !newvert )
+               {
+                  cxr_debug_box(cxr_ab_ptr(abverts,dispedge[dispedge_count-1]), 0.1, colour_error);
+                  break;
+               }
+            }
+            
+            // --------------------
+            // Edges collected
+            
+            v2f va, vb;
+            v2_sub( corner_uvs[1], corner_uvs[0], va );
+            v2_sub( corner_uvs[2], corner_uvs[0], vb );
+            
+            // Connect up the grid
+            //
+            //   0 1 2 3 4
+            //  15 a b c d
+            //  14 e f g h
+            //  13 i j k l
+            //  12 m n o p
+            //
+            //  Example: a := common unused vertex that is connected to
+            //                by 1 and 15. Or y-1, and x-1 on the grid.
+            //           g := c and f common vert ^
+            //
+            int grid[25];
+            
+            for( int j=0; j<5; j++ ) grid[j] = dispedge[j];
+            for( int j=1; j<5; j++ ) grid[j*5+4] = dispedge[j+4];
+            for( int j=0; j<4; j++ ) grid[4*5+3-j] = dispedge[j+9];
+            for( int j=1; j<4; j++ ) grid[j*5] = dispedge[16-j];
+
+            // Grid fill
+            for( int j=1; j<4; j++ )
+            {
+               for( int k=1; k<4; k++ )
+               {
+                  int s0 = grid[(j-1)*5+k],
+                      s1 = grid[j*5+k-1];
+
+                  struct vertinfo *va = &vertinfo[s0],
+                                  *vb = &vertinfo[s1];
+
+                  // Find a common vertex between s0 and s1
+                  
+                  for( int l=0; l<va->con_count; l ++ )
+                  {
+                     for( int m=0; m<vb->con_count; m ++ )
+                     {
+                        int cona = graph[va->con_start+l],
+                            conb = graph[vb->con_start+m];
+
+                        if( cona == conb )
+                        {
+                           if( vertinfo[cona].used || vertinfo[cona].boundary )
+                              continue;
+
+                           grid[ j*5+k ] = cona;
+                           vertinfo[cona].used = 1;
+
+                           goto IL_MATCHED_DISP_INTERIOR_VERT;
+                        }
+                     }
+                  }
+
+                  // Broken displacement
+                  cxr_log( "Broken displacement!\n" );
+                  free( graph );
+                  free( vertinfo );
+                  return;
+
+                  IL_MATCHED_DISP_INTERIOR_VERT:;
+               }
+            }
+
+            // Create brush vertices based on UV map
+            
+            // Create V reference based on first displacement.
+            // TODO: This is not the moststable selection method!
+            //       faces can come in any order, so the first disp will of course
+            //       always vary. Additionaly the triangle can be oriented differently.
+            //
+            //       Improvement can be made by selecting a first disp/triangle based
+            //       on deterministic factors.
+            //
+            if( disp_count == 0 )
+            {
+               struct cxr_texinfo tx;
+               v3f tri_ref[3];
+               v3_copy( cxr_ab_ptr(abverts,dispedge[0]), tri_ref[0] );
+               v3_copy( cxr_ab_ptr(abverts,dispedge[4]), tri_ref[1] );
+               v3_copy( cxr_ab_ptr(abverts,dispedge[8]), tri_ref[2] );
+               cxr_calculate_axis( &tx, tri_ref, corner_uvs, (v2f){512,512} );
+
+               v3_muls( tx.vaxis, -1.0, refv );
+               int v_cardinal = cxr_cardinal( refv, -1 );
+
+               v3_cross( tx.vaxis, tx.uaxis, refn );
+               v3_muls( refn, -tx.winding, refn );
+
+               int n1_cardinal = cxr_cardinal( refn, v_cardinal );
+               
+               //v3_copy( avg_normal, refn );
+               int u_cardinal = 0;
+               if( u_cardinal == n1_cardinal || u_cardinal == v_cardinal ) u_cardinal ++;
+               if( u_cardinal == n1_cardinal || u_cardinal == v_cardinal ) u_cardinal ++;
+
+               v3_zero(refu);
+               refu[u_cardinal] = tx.uaxis[u_cardinal] > 0.0? 1.0: -1.0;
+
+               v3f p0, pv, pu, pn;
+
+               v3_copy( face_center, p0 );
+               v3_muladds( face_center, refn, 1.5, pn );
+               v3_muladds( face_center, refv, 1.5, pv );
+               v3_muladds( face_center, refu, 1.5, pu );
+               
+               if( cxr_settings.debug )
+               {
+                  cxr_debug_line( p0, pn, (v4f){0.0,0.0,1.0,1.0});
+                  cxr_debug_line( p0, pv, (v4f){0.0,1.0,0.0,1.0});
+                  cxr_debug_line( p0, pu, (v4f){1.0,0.0,0.0,1.0});
+                  cxr_debug_line( tri_ref[0], tri_ref[1], (v4f){1.0,1.0,1.0,1.0} );
+                  cxr_debug_line( tri_ref[1], tri_ref[2], (v4f){1.0,1.0,1.0,1.0} );
+                  cxr_debug_line( tri_ref[2], tri_ref[0], (v4f){1.0,1.0,1.0,1.0} );
+               }
+            }
+
+            // Create world cordinates
+            v3f world_corners[8];
+            v2f world_uv[4];
+
+            for( int j=0; j<4; j++ )
+            {
+               v2f local_uv;
+               v2_sub( corner_uvs[j], uv_center, local_uv );
+               v2_copy( corner_uvs[j], world_uv[j] );
+               v2_muls( local_uv, sf, local_uv );
+
+               v3_muls( refu, local_uv[0], world_corners[j] );
+               v3_muladds( world_corners[j], refv, local_uv[1], world_corners[j] );
+               v3_add( face_center, world_corners[j], world_corners[j] );
+            }
+
+            double *colour = colours_random[cxr_range(disp_count,8)];
+
+            for( int j=0; j<4; j++ )
+               v3_muladds( world_corners[j], refn, -1.0, world_corners[j+4] );
+            
+            if( cxr_settings.debug )
+            {
+               cxr_debug_arrow( world_corners[0], world_corners[1], avg_normal, 0.1, colour );
+               cxr_debug_arrow( world_corners[1], world_corners[2], avg_normal, 0.1, colour );
+               cxr_debug_arrow( world_corners[2], world_corners[3], avg_normal, 0.1, colour );
+               cxr_debug_arrow( world_corners[3], world_corners[0], avg_normal, 0.1, colour );
+            }
+
+            /*
+            cxr_debug_arrow( world_corners[0+4], world_corners[1+4], avg_normal, 0.1, colour );
+            cxr_debug_arrow( world_corners[1+4], world_corners[2+4], avg_normal, 0.1, colour );
+            cxr_debug_arrow( world_corners[2+4], world_corners[3+4], avg_normal, 0.1, colour );
+            cxr_debug_arrow( world_corners[3+4], world_corners[0+4], avg_normal, 0.1, colour );
+            */
+
+            // Apply world transform
+            for( int j=0; j<8; j++ )
+            {
+               v3_muls( world_corners[j], cxr_context.scale_factor, world_corners[j] );
+               world_corners[j][2] += cxr_context.offset_z;
+            }
+
+            struct cxr_texinfo texinfo_shared;
+            cxr_calculate_axis( &texinfo_shared, world_corners, world_uv, 
+               (v2f){ matptr->res[0], matptr->res[1] } );
+
+            // Write brush
+            cxr_vdf_node( output, "solid" );
+            cxr_vdf_ki32( output, "id", ++ cxr_context.brush_count ); 
+
+            int sides[6][3] =
+            {{ 0, 1, 2 },
+             { 4, 6, 5 },
+             { 4, 1, 0 },
+             { 7, 0, 3 },
+             { 6, 2, 1 },
+             { 6, 3, 2 }};
+            
+            v3f normals[25];
+            double distances[25];
+            
+            v3f lside0, lside1, lref, vdelta, vworld;
+            double tx, ty;
+
+            for( int j=0; j<5; j++ )
+            {
+               ty = (double)j/(double)(5-1);
+
+               v3_lerp( world_corners[0], world_corners[3], ty, lside0 );
+               v3_lerp( world_corners[1], world_corners[2], ty, lside1 );
+
+               for( int k=0; k<5; k++ )
+               {
+                  int index = j*5+k;
+
+                  tx = (double)k/(double)(5-1);
+                  v3_lerp( lside0, lside1, tx, lref );
+                  v3_muls( cxr_ab_ptr(abverts, grid[index]), cxr_context.scale_factor, vworld );
+                  vworld[2] += cxr_context.offset_z;
+
+                  v3_sub( vworld, lref, vdelta );
+                  v3_copy( vdelta, normals[index] );
+                  v3_normalize( normals[index] );
+                  distances[index] = v3_dot( vdelta, normals[index] );
+               }
+            }
+
+            for( int j=0; j<6; j++ )
+            {
+               int *side = sides[j];
+
+               cxr_vdf_node( output, "side" );
+               cxr_vdf_ki32( output, "id", ++ cxr_context.face_count );
+               cxr_vdf_plane( output, "plane", world_corners[side[2]],
+                                               world_corners[side[1]],
+                                               world_corners[side[0]] );
+               
+               cxr_vdf_kv( output, "material", matptr->vmt_path );
+
+               cxr_vdf_kaxis( output, "uaxis", 
+                     texinfo_shared.uaxis, 
+                     texinfo_shared.offset[0], 
+                     texinfo_shared.scale[0] );
+               cxr_vdf_kaxis( output, "vaxis", 
+                     texinfo_shared.vaxis, 
+                     texinfo_shared.offset[1], 
+                     texinfo_shared.scale[1] );
+
+               cxr_vdf_kdouble( output, "rotation", 0.0 );
+               cxr_vdf_ki32( output, "lightmapscale", cxr_settings.lightmap_scale );
+               cxr_vdf_ki32( output, "smoothing_groups", 0 );
+               
+               if( j == 0 )
+               {
+                  cxr_vdf_node( output, "dispinfo" );
+                  cxr_vdf_ki32( output, "power", 2 );
+                  cxr_vdf_kv3f( output, "startposition", world_corners[0] );
+                  cxr_vdf_ki32( output, "flags", 0 );
+                  cxr_vdf_kdouble( output, "elevation", 0.0 );
+                  cxr_vdf_ki32( output, "subdiv", 0 );
+                  
+                  cxr_vdf_node( output, "normals" );
+                  for( int k=0; k<5; k++ )
+                     cxr_vdf_karrv3f( output, "row", k, &normals[k*5], 5 );
+                  cxr_vdf_edon( output );
+                  
+                  cxr_vdf_node( output, "distances" );
+                  for( int k=0; k<5; k++ )
+                     cxr_vdf_karrdouble( output, "row", k, &distances[k*5], 5 );
+                  cxr_vdf_edon( output );
+                  
+                  // TODO: This might be needed for compiling...
+                  /*
+                  cxr_vdf_node( output, "offsets" );
+                  for( int k=0; k<5; k++ )
+                     cxr_vdf_printf( output, "\"row%d\" \"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\"\n", k );
+                  cxr_vdf_edon( output );
+
+                  cxr_vdf_node( output, "offset_normals" );
+                  for( int k=0; k<5; k++ )
+                     cxr_vdf_printf( output, "\"row%d\" \"0 0 1 0 0 1 0 0 1 0 0 1 0 0 1\"\n", k );
+                  cxr_vdf_edon( output );
+                  
+                  cxr_vdf_node( output, "alphas" );
+                  for( int k=0; k<5; k++ )
+                     cxr_vdf_printf( output, "\"row%d\" \"0 0 0 0 0\"\n", k );
+                  cxr_vdf_edon( output );
+                  
+                  cxr_vdf_node( output, "triangle_tags" );
+                  for( int k=0; k<5-1; k++ )
+                     cxr_vdf_printf( output, "\"row%d\" \"9 9 9 9 9 9 9 9\"\n", k );
+                  cxr_vdf_edon( output );
+                  
+                  cxr_vdf_node( output, "allowed_verts" );
+                  cxr_vdf_printf( output, "\"10\" \"-1 -1 -1 -1 -1 -1 -1 -1 -1 -1\"\n" );
+                  cxr_vdf_edon( output );
+                  */
+                  cxr_vdf_edon( output );
+               }
+
+               cxr_vdf_edon( output );
+            }
+
+            cxr_vdf_node(output, "editor");
+            cxr_vdf_colour255(output,"color", colours_random[cxr_range(cxr_context.brush_count,8)]);
+            cxr_vdf_ki32(output,"visgroupshown",1);
+            cxr_vdf_ki32(output,"visgroupautoshown",1);
+            cxr_vdf_edon(output);
+
+            cxr_vdf_edon( output );
+            disp_count ++;
+         }
+      }
+   }
+
+   // Main loop
+#if 0
+   int pool[25];
+   for( int i=0; i<abverts->count; i++ )
+   {
+      struct vertinfo *info = &vertinfo[i];
+      if( info->boundary || info->used )
+         continue;
+
+      // Gather all vertices in this displacement
+      int poolcount = 1,
+          front_start = 0,
+          front_count = 1;
+      pool[0] = i;
+      info->used = 1;
+
+      IL_GATHER_LOOP:;
+      
+      int new_front_start = poolcount;
+
+      for( int j=0; j<front_count; j++ )
+      {
+         struct vertinfo *frontvert = &vertinfo[pool[front_start+j]];
+
+         for( int k=0; k<frontvert->con_count; k++ )
+         {
+            int conid = graph[frontvert->con_start+k];
+            struct vertinfo *con = &vertinfo[conid];
+
+            if( frontvert->boundary && !con->boundary )
+               continue;
+
+            if( con->used )
+               continue;
+            
+            if( poolcount == 25 )
+               goto IL_DISP_ERROR_COUNT;
+
+            con->used = 1;
+            pool[ poolcount ++ ] = conid;
+         }
+      }
+
+      if( poolcount > new_front_start )
+      {
+         front_start = new_front_start;
+         front_count = poolcount-front_start;
+
+         goto IL_GATHER_LOOP;
+      }
+      
+      if( poolcount != 25 )
+      {
+IL_DISP_ERROR_COUNT:
+         for( int i=0; i<poolcount; i++ )
+            cxr_debug_box( cxr_ab_ptr(abverts,pool[i]), 0.02, colour_error );
+
+         free(graph);
+         free(vertinfo);
+
+         cxr_log("Invalid displacement (>25 verts)\n");
+         return;
+      }
+
+      int corners[4];
+      int corner_count = 0;
+      struct cxr_loop *cornerloops[4];
+      
+      // Find corners, and get their loops (for uvs)
+      // note: the mesh must be split where there is texture seams
+      //       so that two different uv'd loops cant ref the same vertex
+      //
+      for( int j=0; j<poolcount; j++ )
+      {
+         if( vertinfo[pool[j]].corner )
+         {
+            if( corner_count == 4 )
+            {
+               corner_count = -1;
+               break;
+            }
+
+            corners[corner_count] = j;
+            
+            // find loop that includes this vert
+            for( int k=0; k<mesh->loops.count; k++ )
+            {
+               struct cxr_loop *lp = cxr_ab_ptr(&mesh->loops,k);
+               if( lp->index == pool[j] )
+               {
+                  cornerloops[corner_count] = lp;
+                  break;
+               }
+            }
+
+            corner_count ++;
+         }
+      }
+
+      if( corner_count !=4 )
+      {
+         free(graph);
+         free(vertinfo);
+         cxr_log( "Invalid displacement (!=4 corners)\n" );
+         return;
+      }
+
+      int pivot = corners[0];
+   }
+#endif
+
+   free( graph );
+   free( vertinfo );
+}
+
 CXR_API i32 cxr_convert_mesh_to_vmf(struct cxr_input_mesh *src, struct cxr_vdf *output)
 {
    // Split mesh into islands
@@ -1808,6 +2550,30 @@ CXR_API i32 cxr_convert_mesh_to_vmf(struct cxr_input_mesh *src, struct cxr_vdf *
    
    // Preprocessor 2: Displacement break-out
    //  ---------------
+   for( int i=0; i<solids.count; i++ )
+   {
+      struct solidinf *pinf = cxr_ab_ptr(&solids,i);
+      
+      for( int j=0; j<pinf->pmesh->polys.count; j++ )
+      {
+         struct cxr_polygon *poly = cxr_ab_ptr( &pinf->pmesh->polys, j );
+
+         for( int k=0; k<poly->loop_total; k++ )
+         {
+            struct cxr_loop *lp = cxr_ab_ptr( &pinf->pmesh->loops, poly->loop_start+k );
+            struct cxr_edge *edge = cxr_ab_ptr( &pinf->pmesh->edges, lp->edge_index );
+
+            if( edge->freestyle )
+               goto IL_SOLID_IS_DISPLACEMENT;
+         }
+      }
+      
+      continue;
+      IL_SOLID_IS_DISPLACEMENT:;
+      
+      pinf->is_displacement = 1;
+      cxr_write_disp( pinf->pmesh, src, output, &abverts );
+   }
 
    // Preprocessor 3: Breakup non-convex shapes into sub-solids
    //  ---------------
@@ -1818,7 +2584,6 @@ CXR_API i32 cxr_convert_mesh_to_vmf(struct cxr_input_mesh *src, struct cxr_vdf *
       struct solidinf pinf = *(struct solidinf *)cxr_ab_ptr(&solids, i);
 
       if( pinf.is_displacement )
-         // TODO: write displacements here... 
          continue;
 
       while(1)
@@ -1847,6 +2612,8 @@ CXR_API i32 cxr_convert_mesh_to_vmf(struct cxr_input_mesh *src, struct cxr_vdf *
 
                   if( error ) break;
                }
+               else
+                  break;
             }
             else 
                break;
@@ -1859,7 +2626,9 @@ CXR_API i32 cxr_convert_mesh_to_vmf(struct cxr_input_mesh *src, struct cxr_vdf *
       for( int i=0; i<solids.count; i++ )
       {
          struct solidinf *solid = cxr_ab_ptr(&solids,i);
-         cxr_debug_mesh( solid->pmesh, cxr_ab_ptr(&abverts,0), colours_random[cxr_range(i,8)] );
+
+         if( !solid->is_displacement )
+            cxr_debug_mesh( solid->pmesh, cxr_ab_ptr(&abverts,0), colours_random[cxr_range(i,8)] );
       }
    }