]> de.git.xonotic.org Git - voretournament/voretournament.git/blobdiff - misc/mediasource/netradiant-src/contrib/gtkgensurf/genmap.cpp
Move the netradiant and fteqcc sources
[voretournament/voretournament.git] / misc / mediasource / netradiant-src / contrib / gtkgensurf / genmap.cpp
diff --git a/misc/mediasource/netradiant-src/contrib/gtkgensurf/genmap.cpp b/misc/mediasource/netradiant-src/contrib/gtkgensurf/genmap.cpp
new file mode 100644 (file)
index 0000000..6e9c95d
--- /dev/null
@@ -0,0 +1,2040 @@
+/*
+GenSurf plugin for GtkRadiant
+Copyright (C) 2001 David Hyde, Loki software and qeradiant.com
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#include <math.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "gensurf.h"
+
+double  xmin,xmax,ymin,ymax,zmin,zmax;
+double  backface;
+extern double dh, dv;
+FILE  *fmap;
+XYZ     xyz[MAX_ROWS+1][MAX_ROWS+1];
+int     contents;
+int     surface[3];
+
+#include "iundo.h"
+
+#include "refcounted_ptr.h"
+
+#include <vector>
+#include <list>
+#include <map>
+#include <algorithm>
+
+#include "scenelib.h"
+
+scene::Node* h_func_group;
+scene::Node* h_worldspawn;
+
+
+//=============================================================
+// Hydra : snap-to-grid begin
+double CalculateSnapValue(double value)
+{
+       long                snapvalue;
+
+  // simple uncomplicated snapping, rounding both UP and DOWN to the nearest
+       // grid unit.
+       if (SnapToGrid >0)
+       {
+               snapvalue = (int)value / SnapToGrid;
+               if ((long)value % SnapToGrid < (SnapToGrid / 2)) // Snap Downwards if less than halfway between to grid units
+                       value = snapvalue * SnapToGrid; 
+               else // Snap Upwards if more than halfway between to grid units
+                       value = (snapvalue+1) * SnapToGrid;
+       }
+  return value;
+}
+// Hydra : snap-to-grid end
+
+//=============================================================
+bool ValidSurface()
+{
+  if(WaveType == WAVE_BITMAP && !gbmp.colors) return FALSE;
+  if(NH  < 1)        return FALSE;
+  if(NH  > MAX_ROWS) return FALSE;
+  if(NV  < 1)        return FALSE;
+  if(NV  > MAX_ROWS) return FALSE;
+  if(Hll >= Hur)     return FALSE;
+  if(Vll >= Vur)     return FALSE;
+  return TRUE;
+}
+
+//=============================================================
+int MapPatches()
+{
+  int     NH_remain;
+  int     NV_remain;
+  int     NH_patch;
+  int     NV_patch;
+  int     BrushNum = 0;
+  int     i, j, k1, k2, k3;
+  int     i0, j0, ii;
+  char    szOops[128];
+
+  dh = (Hur-Hll)/NH;
+  dv = (Vur-Vll)/NV;
+
+  // Generate control points in pp array to give desired values currently
+  // in p array.
+  switch(Plane)
+  {
+  case PLANE_XY0:
+  case PLANE_XY1:
+    k1 = 0;
+    k2 = 1;
+    k3 = 2;
+    break;
+  case PLANE_XZ0:
+  case PLANE_XZ1:
+    k1 = 0;
+    k2 = 2;
+    k3 = 1;
+    break;
+  case PLANE_YZ0:
+  case PLANE_YZ1:
+    k1 = 1;
+    k2 = 2;
+    k3 = 0;
+    break;
+  }
+  for(i=0; i<=NH; i++)
+  {
+    for(j=0; j<=NV; j++)
+    {
+      xyz[i][j].pp[k1] = xyz[i][j].p[k1];
+      xyz[i][j].pp[k2] = xyz[i][j].p[k2];
+    }
+  }
+  for(i=0; i<=NH; i+=2)
+  {
+    for(j=0; j<=NV; j+=2)
+      xyz[i][j].pp[k3] = xyz[i][j].p[k3];
+  }
+  for(i=1; i<NH; i+=2)
+  {
+    for(j=0; j<=NV; j+=2)
+    {
+      xyz[i][j].pp[k3] = (4*xyz[i][j].p[k3] - xyz[i-1][j].p[k3] - xyz[i+1][j].p[k3])/2;
+    }
+  }
+  for(j=1; j<NV; j+=2)
+  {
+    for(i=0; i<=NH; i+=2)
+    {
+      xyz[i][j].pp[k3] = (4*xyz[i][j].p[k3] - xyz[i][j-1].p[k3] - xyz[i][j+1].p[k3])/2;
+    }
+  }
+  for(i=1; i<NH; i+=2)
+  {
+    for(j=1; j<NV; j+=2)
+    {
+      xyz[i][j].pp[k3] = (16*xyz[i][j].p[k3] - xyz[i-1][j-1].p[k3] - 2*xyz[i][j-1].p[k3]
+        - xyz[i+1][j-1].p[k3] - 2*xyz[i-1][j].p[k3] - 2*xyz[i+1][j].p[k3]
+        - xyz[i-1][j+1].p[k3] - 2*xyz[i][j+1].p[k3] - xyz[i+1][j+1].p[k3])/4;
+    }
+  }
+
+  NH_remain = NH+1;
+  i0 = 0;
+  while(NH_remain > 1)
+  {
+    if(( (NH_remain-1) % 14) == 0)
+      NH_patch = 15;
+    else if(( (NH_remain-1) % 12) == 0)
+      NH_patch = 13;
+    else if(( (NH_remain-1) % 10) == 0)
+      NH_patch = 11;
+    else if(( (NH_remain-1) % 8) == 0)
+      NH_patch = 9;
+    else if(( (NH_remain-1) % 6) == 0)
+      NH_patch = 7;
+    else if(( (NH_remain-1) % 4) == 0)
+      NH_patch = 5;
+    else if(( (NH_remain-1) % 2) == 0)
+      NH_patch = 3;
+    else if(NH_remain > 16)
+      NH_patch = 7;
+    else if(NH_remain > 4)
+      NH_patch = 5;
+    else
+      NH_patch = 3;
+    while( NH_patch > 3 && (NH_patch-1)*dh > 512 )
+      NH_patch -= 2;
+    NH_remain -= (NH_patch-1);
+    if(NH_remain < 0)
+    {
+      sprintf(szOops,"Oops... screwed up with NH=%d",NH);
+      g_FuncTable.m_pfnMessageBox(NULL,szOops,"Uh oh");
+    }
+    NV_remain = NV+1;
+    j0 = 0;
+    while(NV_remain > 1)
+    {
+      if(( (NV_remain-1) % 14) == 0)
+        NV_patch = 15;
+      else if(( (NV_remain-1) % 12) == 0)
+        NV_patch = 13;
+      else if(( (NV_remain-1) % 10) == 0)
+        NV_patch = 11;
+      else if(( (NV_remain-1) % 8) == 0)
+        NV_patch = 9;
+      else if(( (NV_remain-1) % 6) == 0)
+        NV_patch = 7;
+      else if(( (NV_remain-1) % 4) == 0)
+        NV_patch = 5;
+      else if(( (NV_remain-1) % 2) == 0)
+        NV_patch = 3;
+      else if(NV_remain > 16)
+        NV_patch = 7;
+      else if(NV_remain > 4)
+        NV_patch = 5;
+      else
+        NV_patch = 3;
+      while( NV_patch > 3 && (NV_patch-1)*dh > 512 )
+        NV_patch -= 2;
+      NV_remain -= (NV_patch-1);
+      if(NV_remain < 0)
+      {
+        sprintf(szOops,"Oops... screwed up with NV=%d",NV);
+        g_FuncTable.m_pfnMessageBox(NULL,szOops,"Uh oh");
+      }
+
+      scene::Node* patch = MakePatch();
+#if 0
+      b->pPatch->setDims(NH_patch, NV_patch);
+      for(i=0; i<NH_patch; i++)
+      {
+        switch(Plane)
+        {
+        case PLANE_XY1:
+        case PLANE_XZ0:
+        case PLANE_YZ1:
+          ii = i0 + NH_patch - 1 - i;
+          break;
+        default:
+          ii = i0+i;
+        }
+        for(j=0; j<NV_patch; j++)
+        {
+          b->pPatch->ctrlAt(COL,i,j)[0] = (float)xyz[ii][j0+j].pp[0];
+          b->pPatch->ctrlAt(COL,i,j)[1] = (float)xyz[ii][j0+j].pp[1];
+          b->pPatch->ctrlAt(COL,i,j)[2] = (float)xyz[ii][j0+j].pp[2];
+          b->pPatch->ctrlAt(COL,i,j)[3] = (float)i;
+          b->pPatch->ctrlAt(COL,i,j)[4] = (float)j;
+        }
+      }
+      b->pPatch->UpdateCachedData();
+#endif
+      BrushNum++;
+      j0 += NV_patch-1;
+    }
+    i0 += NH_patch-1;
+  }
+  return BrushNum;
+}
+
+//=============================================================
+void MapBrushes()
+{
+  char  hint[128];
+  char  skip[128];
+  char  sidetext[64];
+  char  surftext[64];
+  char  surftext2[64];
+  char  surft[64];
+  float Steep;
+  vec3_t  PlaneNormal,SurfNormal;
+  vec3_t  t[2];
+  int   i, j, k;
+  int   surf;
+  bool  CheckAngle;
+  BRUSH brush;
+  XYZ   v[8];
+
+  strcpy(surftext,Texture[Game][0]);
+  strcpy(sidetext,(strlen(Texture[Game][1]) ? Texture[Game][1] : Texture[Game][0]));
+  strcpy(surftext2,(strlen(Texture[Game][2]) ? Texture[Game][2] : Texture[Game][0]));
+
+  // if surftext2 is identical to surftext, there's no need to
+  // check surface angle
+  if(!g_strcasecmp(surftext,surftext2))
+    CheckAngle = FALSE;
+  else
+  {
+    CheckAngle = TRUE;
+    Steep = (float)cos((double)SlantAngle/57.2957795);
+    switch(Plane)
+    {
+    case PLANE_XY0: PlaneNormal[0]= 0.;PlaneNormal[1]= 0.;PlaneNormal[2]= 1.;break;
+    case PLANE_XY1: PlaneNormal[0]= 0.;PlaneNormal[1]= 0.;PlaneNormal[2]=-1.;break;
+    case PLANE_XZ0: PlaneNormal[0]= 0.;PlaneNormal[1]= 1.;PlaneNormal[2]= 1.;break;
+    case PLANE_XZ1: PlaneNormal[0]= 0.;PlaneNormal[1]=-1.;PlaneNormal[2]= 1.;break;
+    case PLANE_YZ0: PlaneNormal[0]= 1.;PlaneNormal[1]= 0.;PlaneNormal[2]= 1.;break;
+    case PLANE_YZ1: PlaneNormal[0]=-1.;PlaneNormal[1]= 0.;PlaneNormal[2]= 1.;break;
+    }
+  }
+  
+  OpenFuncGroup();
+
+  for(i=0; i<NH; i++)
+  {
+    for(j=0; j<NV; j++)
+    {
+      if( (i+j) % 2 )
+      {
+        VectorCopy(xyz[i  ][j  ].p, v[0].p);
+        switch(Plane)
+        {
+        case PLANE_XY1:
+        case PLANE_XZ1:
+        case PLANE_YZ1:
+          VectorCopy(xyz[i+1][j  ].p, v[1].p);
+          VectorCopy(xyz[i+1][j+1].p, v[2].p);
+          break;
+        default:
+          VectorCopy(xyz[i+1][j+1].p, v[1].p);
+          VectorCopy(xyz[i+1][j  ].p, v[2].p);
+        }
+      }
+      else
+      {
+        VectorCopy(xyz[i  ][j  ].p, v[0].p);
+        switch(Plane)
+        {
+        case PLANE_XY1:
+        case PLANE_XZ1:
+        case PLANE_YZ1:
+          VectorCopy(xyz[i+1][j  ].p, v[1].p);
+          VectorCopy(xyz[i  ][j+1].p, v[2].p);
+          break;
+        default:
+          VectorCopy(xyz[i  ][j+1].p, v[1].p);
+          VectorCopy(xyz[i+1][j  ].p, v[2].p);
+        }
+      }
+      VectorCopy(v[0].p,v[3].p);
+      VectorCopy(v[1].p,v[4].p);
+      VectorCopy(v[2].p,v[5].p);
+      switch(Plane)
+      {
+      case PLANE_XZ0:
+      case PLANE_XZ1:
+        v[0].p[1] = backface;
+        v[1].p[1] = backface;
+        v[2].p[1] = backface;
+        break;
+      case PLANE_YZ0:
+      case PLANE_YZ1:
+        v[3].p[0] = backface;
+        v[4].p[0] = backface;
+        v[5].p[0] = backface;
+        break;
+      default:
+        v[3].p[2] = backface;
+        v[4].p[2] = backface;
+        v[5].p[2] = backface;
+      }
+
+      brush.Number   = i*NV*2+j*2;
+      brush.NumFaces = 5;
+      XYZtoV(&v[0],&brush.face[0].v[0]);
+      XYZtoV(&v[3],&brush.face[0].v[1]);
+      XYZtoV(&v[4],&brush.face[0].v[2]);
+      strcpy(brush.face[0].texture,
+        (strlen(Texture[Game][1]) ? Texture[Game][1] : Texture[Game][0]));
+      brush.face[0].Shift[0] = (float)TexOffset[0];
+      brush.face[0].Shift[1] = (float)TexOffset[1];
+      brush.face[0].Rotate   = 0.;
+      brush.face[0].Scale[0] = (float)TexScale[0];
+      brush.face[0].Scale[1] = (float)TexScale[1];
+      brush.face[0].Contents = contents;
+      brush.face[0].Surface  = surface[1];
+      brush.face[0].Value    = 0;
+
+      XYZtoV(&v[1],&brush.face[1].v[0]);
+      XYZtoV(&v[4],&brush.face[1].v[1]);
+      XYZtoV(&v[5],&brush.face[1].v[2]);
+      strcpy(brush.face[1].texture,
+        (strlen(Texture[Game][1]) ? Texture[Game][1] : Texture[Game][0]));
+      brush.face[1].Shift[0] = (float)TexOffset[0];
+      brush.face[1].Shift[1] = (float)TexOffset[1];
+      brush.face[1].Rotate   = 0.;
+      brush.face[1].Scale[0] = (float)TexScale[0];
+      brush.face[1].Scale[1] = (float)TexScale[1];
+      brush.face[1].Contents = contents;
+      brush.face[1].Surface  = surface[1];
+      brush.face[1].Value    = 0;
+
+      XYZtoV(&v[2],&brush.face[2].v[0]);
+      XYZtoV(&v[5],&brush.face[2].v[1]);
+      XYZtoV(&v[3],&brush.face[2].v[2]);
+      strcpy(brush.face[2].texture,
+        (strlen(Texture[Game][1]) ? Texture[Game][1] : Texture[Game][0]));
+      brush.face[2].Shift[0] = (float)TexOffset[0];
+      brush.face[2].Shift[1] = (float)TexOffset[1];
+      brush.face[2].Rotate   = 0.;
+      brush.face[2].Scale[0] = (float)TexScale[0];
+      brush.face[2].Scale[1] = (float)TexScale[1];
+      brush.face[2].Contents = contents;
+      brush.face[2].Surface  = surface[1];
+      brush.face[2].Value    = 0;
+
+      if(CheckAngle && (Plane==PLANE_XZ0 || Plane==PLANE_XZ1))
+      {
+        XYZVectorSubtract(v[4].p,v[3].p,t[0]);
+        XYZVectorSubtract(v[5].p,v[4].p,t[1]);
+        CrossProduct(t[0],t[1],SurfNormal);
+        VectorNormalize(SurfNormal,SurfNormal);
+        if(DotProduct(SurfNormal,PlaneNormal) < Steep)
+        {
+          strcpy(surft,surftext2);
+          surf = surface[2];
+        }
+        else
+        {
+          strcpy(surft,surftext);
+          surf = surface[0];
+        }
+      }
+      else
+      {
+        strcpy(surft,surftext);
+        surf = surface[0];
+      }
+
+      XYZtoV(&v[3],&brush.face[3].v[0]);
+      XYZtoV(&v[5],&brush.face[3].v[1]);
+      XYZtoV(&v[4],&brush.face[3].v[2]);
+      strcpy(brush.face[3].texture,
+        (Plane==PLANE_XZ0 || Plane==PLANE_XZ1 ? surft : sidetext));
+      brush.face[3].Shift[0] = (float)TexOffset[0];
+      brush.face[3].Shift[1] = (float)TexOffset[1];
+      brush.face[3].Rotate   = 0.;
+      brush.face[3].Scale[0] = (float)TexScale[0];
+      brush.face[3].Scale[1] = (float)TexScale[1];
+      brush.face[3].Contents = contents;
+      brush.face[3].Surface  = (Plane==PLANE_XZ0 || Plane==PLANE_XZ1 ? surf : surface[1]);
+      brush.face[3].Value    = (Plane==PLANE_XZ0 || Plane==PLANE_XZ1 ? ArghRad2 : 0);
+
+      if(CheckAngle && Plane!=PLANE_XZ0 && Plane!=PLANE_XZ1)
+      {
+        XYZVectorSubtract(v[2].p,v[0].p,t[0]);
+        XYZVectorSubtract(v[1].p,v[2].p,t[1]);
+        CrossProduct(t[0],t[1],SurfNormal);
+        VectorNormalize(SurfNormal,SurfNormal);
+        if(DotProduct(SurfNormal,PlaneNormal) < Steep)
+        {
+          strcpy(surft,surftext2);
+          surf = surface[2];
+        }
+        else
+        {
+          strcpy(surft,surftext);
+          surf = surface[0];
+        }
+      }
+      else
+      {
+        strcpy(surft,surftext);
+        surf = surface[0];
+      }
+
+      XYZtoV(&v[0],&brush.face[4].v[0]);
+      XYZtoV(&v[1],&brush.face[4].v[1]);
+      XYZtoV(&v[2],&brush.face[4].v[2]);
+      strcpy(brush.face[4].texture,
+        (Plane==PLANE_XZ0 || Plane==PLANE_XZ1 ? sidetext : surft));
+      brush.face[4].Shift[0] = (float)TexOffset[0];
+      brush.face[4].Shift[1] = (float)TexOffset[1];
+      brush.face[4].Rotate   = 0.;
+      brush.face[4].Scale[0] = (float)TexScale[0];
+      brush.face[4].Scale[1] = (float)TexScale[1];
+      brush.face[4].Contents = contents;
+      brush.face[4].Surface  = (Plane==PLANE_XZ0 || Plane==PLANE_XZ1 ? surface[1] : surf);
+      brush.face[4].Value    = (Plane==PLANE_XZ0 || Plane==PLANE_XZ1 ? 0 : ArghRad2);
+
+      MakeBrush(&brush);
+      if( (i+j) %2 )
+      {
+        VectorCopy(xyz[i  ][j+1].p,v[0].p);
+        switch(Plane)
+        {
+        case PLANE_XY1:
+        case PLANE_XZ1:
+        case PLANE_YZ1:
+          VectorCopy(xyz[i  ][j  ].p,v[1].p);
+          VectorCopy(xyz[i+1][j+1].p,v[2].p);
+          break;
+        default:
+          VectorCopy(xyz[i+1][j+1].p,v[1].p);
+          VectorCopy(xyz[i  ][j  ].p,v[2].p);
+        }
+      }
+      else
+      {
+        VectorCopy(xyz[i  ][j+1].p,v[0].p);
+        switch(Plane)
+        {
+        case PLANE_XY1:
+        case PLANE_XZ1:
+        case PLANE_YZ1:
+          VectorCopy(xyz[i+1][j  ].p,v[1].p);
+          VectorCopy(xyz[i+1][j+1].p,v[2].p);
+          break;
+        default:
+          VectorCopy(xyz[i+1][j+1].p,v[1].p);
+          VectorCopy(xyz[i+1][j  ].p,v[2].p);
+        }
+      }
+      VectorCopy(v[0].p,v[3].p);
+      VectorCopy(v[1].p,v[4].p);
+      VectorCopy(v[2].p,v[5].p);
+      switch(Plane)
+      {
+      case PLANE_XZ0:
+      case PLANE_XZ1:
+        v[0].p[1] = backface;
+        v[1].p[1] = backface;
+        v[2].p[1] = backface;
+        break;
+      case PLANE_YZ0:
+      case PLANE_YZ1:
+        v[3].p[0] = backface;
+        v[4].p[0] = backface;
+        v[5].p[0] = backface;
+        break;
+      default:
+        v[3].p[2] = backface;
+        v[4].p[2] = backface;
+        v[5].p[2] = backface;
+      }
+      brush.Number   = i*NV*2+j*2+1;
+      brush.NumFaces = 5;
+      XYZtoV(&v[0],&brush.face[0].v[0]);
+      XYZtoV(&v[3],&brush.face[0].v[1]);
+      XYZtoV(&v[4],&brush.face[0].v[2]);
+      strcpy(brush.face[0].texture,
+        (strlen(Texture[Game][1]) ? Texture[Game][1] : Texture[Game][0]));
+      brush.face[0].Shift[0] = (float)TexOffset[0];
+      brush.face[0].Shift[1] = (float)TexOffset[1];
+      brush.face[0].Rotate   = 0.;
+      brush.face[0].Scale[0] = (float)TexScale[0];
+      brush.face[0].Scale[1] = (float)TexScale[1];
+      brush.face[0].Contents = contents;
+      brush.face[0].Surface  = surface[1];
+      brush.face[0].Value    = 0;
+
+      XYZtoV(&v[1],&brush.face[1].v[0]);
+      XYZtoV(&v[4],&brush.face[1].v[1]);
+      XYZtoV(&v[5],&brush.face[1].v[2]);
+      strcpy(brush.face[1].texture,
+        (strlen(Texture[Game][1]) ? Texture[Game][1] : Texture[Game][0]));
+      brush.face[1].Shift[0] = (float)TexOffset[0];
+      brush.face[1].Shift[1] = (float)TexOffset[1];
+      brush.face[1].Rotate   = 0.;
+      brush.face[1].Scale[0] = (float)TexScale[0];
+      brush.face[1].Scale[1] = (float)TexScale[1];
+      brush.face[1].Contents = contents;
+      brush.face[1].Surface  = surface[1];
+      brush.face[1].Value    = 0;
+
+      XYZtoV(&v[2],&brush.face[2].v[0]);
+      XYZtoV(&v[5],&brush.face[2].v[1]);
+      XYZtoV(&v[3],&brush.face[2].v[2]);
+      strcpy(brush.face[2].texture,
+        (strlen(Texture[Game][1]) ? Texture[Game][1] : Texture[Game][0]));
+      brush.face[2].Shift[0] = (float)TexOffset[0];
+      brush.face[2].Shift[1] = (float)TexOffset[1];
+      brush.face[2].Rotate   = 0.;
+      brush.face[2].Scale[0] = (float)TexScale[0];
+      brush.face[2].Scale[1] = (float)TexScale[1];
+      brush.face[2].Contents = contents;
+      brush.face[2].Surface  = surface[1];
+      brush.face[2].Value    = 0;
+
+      if(CheckAngle && (Plane==PLANE_XZ0 || Plane==PLANE_XZ1))
+      {
+        XYZVectorSubtract(v[4].p,v[3].p,t[0]);
+        XYZVectorSubtract(v[5].p,v[4].p,t[1]);
+        CrossProduct(t[0],t[1],SurfNormal);
+        VectorNormalize(SurfNormal,SurfNormal);
+        if(DotProduct(SurfNormal,PlaneNormal) < Steep)
+        {
+          strcpy(surft,surftext2);
+          surf = surface[2];
+        }
+        else
+        {
+          strcpy(surft,surftext);
+          surf = surface[0];
+        }
+      }
+      else
+      {
+        strcpy(surft,surftext);
+        surf = surface[0];
+      }
+      XYZtoV(&v[3],&brush.face[3].v[0]);
+      XYZtoV(&v[5],&brush.face[3].v[1]);
+      XYZtoV(&v[4],&brush.face[3].v[2]);
+      strcpy(brush.face[3].texture,
+        (Plane==PLANE_XZ0 || Plane==PLANE_XZ1 ? surft : sidetext));
+      brush.face[3].Shift[0] = (float)TexOffset[0];
+      brush.face[3].Shift[1] = (float)TexOffset[1];
+      brush.face[3].Rotate   = 0.;
+      brush.face[3].Scale[0] = (float)TexScale[0];
+      brush.face[3].Scale[1] = (float)TexScale[1];
+      brush.face[3].Contents = contents;
+      brush.face[3].Surface  = (Plane==PLANE_XZ0 || Plane==PLANE_XZ1 ? surf : surface[1]);
+      brush.face[3].Value    = (Plane==PLANE_XZ0 || Plane==PLANE_XZ1 ? ArghRad2 : 0);
+
+      if(CheckAngle && Plane!=PLANE_XZ0 && Plane!=PLANE_XZ1)
+      {
+        XYZVectorSubtract(v[2].p,v[0].p,t[0]);
+        XYZVectorSubtract(v[1].p,v[2].p,t[1]);
+        CrossProduct(t[0],t[1],SurfNormal);
+        VectorNormalize(SurfNormal,SurfNormal);
+        if(DotProduct(SurfNormal,PlaneNormal) < Steep)
+        {
+          strcpy(surft,surftext2);
+          surf = surface[2];
+        }
+        else
+        {
+          strcpy(surft,surftext);
+          surf = surface[0];
+        }
+      }
+      else
+      {
+        strcpy(surft,surftext);
+        surf = surface[0];
+      }
+      XYZtoV(&v[0],&brush.face[4].v[0]);
+      XYZtoV(&v[1],&brush.face[4].v[1]);
+      XYZtoV(&v[2],&brush.face[4].v[2]);
+      strcpy(brush.face[4].texture,
+        (Plane==PLANE_XZ0 || Plane==PLANE_XZ1 ? sidetext : surft));
+      brush.face[4].Shift[0] = (float)TexOffset[0];
+      brush.face[4].Shift[1] = (float)TexOffset[1];
+      brush.face[4].Rotate   = 0.;
+      brush.face[4].Scale[0] = (float)TexScale[0];
+      brush.face[4].Scale[1] = (float)TexScale[1];
+      brush.face[4].Contents = contents;
+      brush.face[4].Surface  = (Plane==PLANE_XZ0 || Plane==PLANE_XZ1 ? surface[1] : surf);
+      brush.face[4].Value    = (Plane==PLANE_XZ0 || Plane==PLANE_XZ1 ? 0 : ArghRad2);
+
+      MakeBrush(&brush);
+    }
+  }
+  CloseFuncGroup();
+
+  if( AddHints || GimpHints )
+  {
+    int detail, i1, j1, N;
+    double front;
+
+    switch(Game)
+    {
+    case HALFLIFE:
+      strcpy(hint,"HINT");
+      strcpy(skip,"HINT");
+      break;
+    case SIN:
+      strcpy(hint,"generic/misc/hint");
+      strcpy(skip,"generic/misc/skip");
+      break;
+    case HERETIC2:
+      strcpy(hint,"general/hint");
+      strcpy(skip,"general/hint");   // Heretic2 doesn't have a skip texture
+      break;
+    case KINGPIN:
+      strcpy(hint,"common/0_hint");
+      strcpy(skip,"common/0_skip");
+      break;
+    case GENESIS3D:
+      strcpy(hint,"hint");
+      strcpy(skip,"hint");
+      break;
+    case QUAKE3:
+      strcpy(hint,"textures/common/hint");
+      strcpy(skip,"textures/common/skip");
+      break;
+    default:
+      strcpy(hint,"e1u1/hint");
+      strcpy(skip,"e1u1/skip");
+    }
+
+    OpenFuncGroup();
+
+    if(AddHints==1)
+    {
+      detail = CONTENTS_DETAIL;
+      N = 0;
+      for(i=0; i<NH; i++)
+      {
+        i1 = i+1;
+        
+        for(j=0; j<NV; j++)
+        {
+          
+          // For detail hint brushes, no need to use a hint brush over
+          // EVERY grid square... it would be redundant. Instead use
+          // a checkerboard pattern
+          if((i+j)%2) continue;
+          
+          j1 = j+1;
+          
+          VectorCopy(xyz[i  ][j  ].p, v[0].p);
+          switch(Plane)
+          {
+          case PLANE_XY1:
+          case PLANE_XZ1:
+          case PLANE_YZ1:
+            VectorCopy(xyz[i1][j ].p, v[1].p);
+            VectorCopy(xyz[i1][j1].p, v[2].p);
+            VectorCopy(xyz[i ][j1].p, v[3].p);
+            break;
+          default:
+            VectorCopy(xyz[i ][j1].p, v[1].p);
+            VectorCopy(xyz[i1][j1].p, v[2].p);
+            VectorCopy(xyz[i1][j ].p, v[3].p);
+          }
+          
+          VectorCopy(v[0].p,v[4].p);
+          VectorCopy(v[1].p,v[5].p);
+          VectorCopy(v[2].p,v[6].p);
+          VectorCopy(v[3].p,v[7].p);
+          
+          switch(Plane)
+          {
+          case PLANE_XY1:
+            front  = LessThan(zmin,32.);
+            v[4].p[2] = backface;
+            v[5].p[2] = backface;
+            v[6].p[2] = backface;
+            v[7].p[2] = backface;
+            break;
+          case PLANE_XZ0:
+            front  = MoreThan(ymax,32.);
+            v[0].p[1] = backface;
+            v[1].p[1] = backface;
+            v[2].p[1] = backface;
+            v[3].p[1] = backface;
+            break;
+          case PLANE_XZ1:
+            front  = LessThan(ymin,32.);
+            v[0].p[1] = backface;
+            v[1].p[1] = backface;
+            v[2].p[1] = backface;
+            v[3].p[1] = backface;
+            break;
+          case PLANE_YZ0:
+            front  = MoreThan(xmax,32.);
+            v[4].p[0] = backface;
+            v[5].p[0] = backface;
+            v[6].p[0] = backface;
+            v[7].p[0] = backface;
+            break;
+          case PLANE_YZ1:
+            front  = LessThan(xmin,32.);
+            v[4].p[0] = backface;
+            v[5].p[0] = backface;
+            v[6].p[0] = backface;
+            v[7].p[0] = backface;
+            break;
+          default:
+            front  = MoreThan(zmax,32.);
+            v[4].p[2] = backface;
+            v[5].p[2] = backface;
+            v[6].p[2] = backface;
+            v[7].p[2] = backface;
+          }
+          
+          switch(Plane)
+          {
+          case PLANE_XZ0:
+          case PLANE_XZ1:
+            v[4].p[1] = front;
+            v[5].p[1] = v[4].p[1];
+            v[6].p[1] = v[4].p[1];
+            v[7].p[1] = v[4].p[1];
+            break;
+          case PLANE_YZ0:
+          case PLANE_YZ1:
+            v[0].p[0] = front;
+            v[1].p[0] = v[0].p[0];
+            v[2].p[0] = v[0].p[0];
+            v[3].p[0] = v[0].p[0];
+            break;
+          default:
+            v[0].p[2] = front;
+            v[1].p[2] = v[0].p[2];
+            v[2].p[2] = v[0].p[2];
+            v[3].p[2] = v[0].p[2];
+          }
+          
+          brush.NumFaces = 6;
+          brush.Number   = N;
+          XYZtoV(&v[0],&brush.face[0].v[0]);
+          XYZtoV(&v[1],&brush.face[0].v[1]);
+          XYZtoV(&v[2],&brush.face[0].v[2]);
+          strcpy(brush.face[0].texture,skip);
+          brush.face[0].Shift[0] = 0.;
+          brush.face[0].Shift[1] = 0.;
+          brush.face[0].Rotate   = 0.;
+          brush.face[0].Scale[0] = 1.;
+          brush.face[0].Scale[1] = 1.;
+          brush.face[0].Contents = detail;
+          brush.face[0].Surface  = SURF_SKIP;
+          brush.face[0].Value    = 0;
+          
+          XYZtoV(&v[4],&brush.face[1].v[0]);
+          XYZtoV(&v[7],&brush.face[1].v[1]);
+          XYZtoV(&v[6],&brush.face[1].v[2]);
+          strcpy(brush.face[1].texture,skip);
+          brush.face[1].Shift[0] = 0.;
+          brush.face[1].Shift[1] = 0.;
+          brush.face[1].Rotate   = 0.;
+          brush.face[1].Scale[0] = 1.;
+          brush.face[1].Scale[1] = 1.;
+          brush.face[1].Contents = detail;
+          brush.face[1].Surface  = SURF_SKIP;
+          brush.face[1].Value    = 0;
+          
+          XYZtoV(&v[0],&brush.face[2].v[0]);
+          XYZtoV(&v[4],&brush.face[2].v[1]);
+          XYZtoV(&v[5],&brush.face[2].v[2]);
+          strcpy(brush.face[2].texture,hint);
+          brush.face[2].Shift[0] = 0.;
+          brush.face[2].Shift[1] = 0.;
+          brush.face[2].Rotate   = 0.;
+          brush.face[2].Scale[0] = 1.;
+          brush.face[2].Scale[1] = 1.;
+          brush.face[2].Contents = detail;
+          brush.face[2].Surface  = SURF_HINT;
+          brush.face[2].Value    = 0;
+          
+          XYZtoV(&v[1],&brush.face[3].v[0]);
+          XYZtoV(&v[5],&brush.face[3].v[1]);
+          XYZtoV(&v[6],&brush.face[3].v[2]);
+          strcpy(brush.face[3].texture,hint);
+          brush.face[3].Shift[0] = 0.;
+          brush.face[3].Shift[1] = 0.;
+          brush.face[3].Rotate   = 0.;
+          brush.face[3].Scale[0] = 1.;
+          brush.face[3].Scale[1] = 1.;
+          brush.face[3].Contents = detail;
+          brush.face[3].Surface  = SURF_HINT;
+          brush.face[3].Value    = 0;
+          
+          XYZtoV(&v[2],&brush.face[4].v[0]);
+          XYZtoV(&v[6],&brush.face[4].v[1]);
+          XYZtoV(&v[7],&brush.face[4].v[2]);
+          strcpy(brush.face[4].texture,hint);
+          brush.face[4].Shift[0] = 0.;
+          brush.face[4].Shift[1] = 0.;
+          brush.face[4].Rotate   = 0.;
+          brush.face[4].Scale[0] = 1.;
+          brush.face[4].Scale[1] = 1.;
+          brush.face[4].Contents = detail;
+          brush.face[4].Surface  = SURF_HINT;
+          brush.face[4].Value    = 0;
+          
+          XYZtoV(&v[3],&brush.face[5].v[0]);
+          XYZtoV(&v[7],&brush.face[5].v[1]);
+          XYZtoV(&v[4],&brush.face[5].v[2]);
+          strcpy(brush.face[5].texture,hint);
+          brush.face[5].Shift[0] = 0.;
+          brush.face[5].Shift[1] = 0.;
+          brush.face[5].Rotate   = 0.;
+          brush.face[5].Scale[0] = 1.;
+          brush.face[5].Scale[1] = 1.;
+          brush.face[5].Contents = detail;
+          brush.face[5].Surface  = SURF_HINT;
+          brush.face[5].Value    = 0;
+          
+          MakeBrush(&brush);
+          N++;
+        }
+      }
+    }
+    if(GimpHints)
+    {
+      N = 0;
+      // these brush parameters never change
+      brush.NumFaces = 5;
+      for(i=0; i<6; i++)
+      {
+        strcpy(brush.face[i].texture,hint);
+        brush.face[i].Shift[0] = 0.;
+        brush.face[i].Shift[1] = 0.;
+        brush.face[i].Rotate   = 0.;
+        brush.face[i].Scale[0] = 1.;
+        brush.face[i].Scale[1] = 1.;
+        brush.face[i].Contents = 0;
+        brush.face[i].Surface  = SURF_HINT;
+        brush.face[i].Value    = 0;
+      }
+      for(i=0; i<NH; i++)
+      {
+        for(j=0; j<NV; j++)
+        {
+          for(k=0; k<2; k++)
+          {
+            if(k==0)
+            {
+              if( (i+j) % 2 )
+              {
+                VectorCopy(xyz[i  ][j  ].p, v[0].p);
+                switch(Plane)
+                {
+                case PLANE_XY1:
+                case PLANE_XZ1:
+                case PLANE_YZ1:
+                  VectorCopy(xyz[i+1][j  ].p, v[1].p);
+                  VectorCopy(xyz[i+1][j+1].p, v[2].p);
+                  break;
+                default:
+                  VectorCopy(xyz[i+1][j+1].p, v[1].p);
+                  VectorCopy(xyz[i+1][j  ].p, v[2].p);
+                }
+              }
+              else
+              {
+                VectorCopy(xyz[i  ][j  ].p, v[0].p);
+                switch(Plane)
+                {
+                case PLANE_XY1:
+                case PLANE_XZ1:
+                case PLANE_YZ1:
+                  VectorCopy(xyz[i+1][j  ].p, v[1].p);
+                  VectorCopy(xyz[i  ][j+1].p, v[2].p);
+                  break;
+                default:
+                  VectorCopy(xyz[i  ][j+1].p, v[1].p);
+                  VectorCopy(xyz[i+1][j  ].p, v[2].p);
+                }
+              }
+            }
+            else
+            {
+              if( (i+j) %2 )
+              {
+                VectorCopy(xyz[i  ][j+1].p,v[0].p);
+                switch(Plane)
+                {
+                case PLANE_XY1:
+                case PLANE_XZ1:
+                case PLANE_YZ1:
+                  VectorCopy(xyz[i  ][j  ].p,v[1].p);
+                  VectorCopy(xyz[i+1][j+1].p,v[2].p);
+                  break;
+                default:
+                  VectorCopy(xyz[i+1][j+1].p,v[1].p);
+                  VectorCopy(xyz[i  ][j  ].p,v[2].p);
+                }
+              }
+              else
+              {
+                VectorCopy(xyz[i  ][j+1].p,v[0].p);
+                switch(Plane)
+                {
+                case PLANE_XY1:
+                case PLANE_XZ1:
+                case PLANE_YZ1:
+                  VectorCopy(xyz[i+1][j  ].p,v[1].p);
+                  VectorCopy(xyz[i+1][j+1].p,v[2].p);
+                  break;
+                default:
+                  VectorCopy(xyz[i+1][j+1].p,v[1].p);
+                  VectorCopy(xyz[i+1][j  ].p,v[2].p);
+                }
+              }
+            }
+            VectorCopy(v[0].p,v[3].p);
+            VectorCopy(v[1].p,v[4].p);
+            VectorCopy(v[2].p,v[5].p);
+            switch(Plane)
+            {
+            case PLANE_XY0:
+              v[0].p[2] += HINT_OFFSET;
+              v[1].p[2] += HINT_OFFSET;
+              v[2].p[2] += HINT_OFFSET;
+//              v[3].p[2] = backface;
+//              v[4].p[2] = backface;
+//              v[5].p[2] = backface;
+              break;
+            case PLANE_XY1:
+              v[0].p[2] -= HINT_OFFSET;
+              v[1].p[2] -= HINT_OFFSET;
+              v[2].p[2] -= HINT_OFFSET;
+//              v[3].p[2] = backface;
+//              v[4].p[2] = backface;
+//              v[5].p[2] = backface;
+              break;
+            case PLANE_XZ0:
+//              v[0].p[1] = backface;
+//              v[1].p[1] = backface;
+//              v[2].p[1] = backface;
+              v[3].p[1] += HINT_OFFSET;
+              v[4].p[1] += HINT_OFFSET;
+              v[5].p[1] += HINT_OFFSET;
+              break;            
+            case PLANE_XZ1:
+//              v[0].p[1] = backface;
+//              v[1].p[1] = backface;
+//              v[2].p[1] = backface;
+              v[3].p[1] -= HINT_OFFSET;
+              v[4].p[1] -= HINT_OFFSET;
+              v[5].p[1] -= HINT_OFFSET;
+              break;
+            case PLANE_YZ0:
+              v[0].p[0] += HINT_OFFSET;
+              v[1].p[0] += HINT_OFFSET;
+              v[2].p[0] += HINT_OFFSET;
+//              v[3].p[0] = backface;
+//              v[4].p[0] = backface;
+//              v[5].p[0] = backface;
+              break;
+            case PLANE_YZ1:
+              v[0].p[0] -= HINT_OFFSET;
+              v[1].p[0] -= HINT_OFFSET;
+              v[2].p[0] -= HINT_OFFSET;
+//              v[3].p[0] = backface;
+//              v[4].p[0] = backface;
+//              v[5].p[0] = backface;
+              break;
+            }
+            brush.Number   = N;
+            XYZtoV(&v[0],&brush.face[0].v[0]);
+            XYZtoV(&v[3],&brush.face[0].v[1]);
+            XYZtoV(&v[4],&brush.face[0].v[2]);
+  
+            XYZtoV(&v[1],&brush.face[1].v[0]);
+            XYZtoV(&v[4],&brush.face[1].v[1]);
+            XYZtoV(&v[5],&brush.face[1].v[2]);
+
+            XYZtoV(&v[2],&brush.face[2].v[0]);
+            XYZtoV(&v[5],&brush.face[2].v[1]);
+            XYZtoV(&v[3],&brush.face[2].v[2]);
+  
+            XYZtoV(&v[3],&brush.face[3].v[0]);
+            XYZtoV(&v[5],&brush.face[3].v[1]);
+            XYZtoV(&v[4],&brush.face[3].v[2]);
+
+            XYZtoV(&v[0],&brush.face[4].v[0]);
+            XYZtoV(&v[1],&brush.face[4].v[1]);
+            XYZtoV(&v[2],&brush.face[4].v[2]);
+
+            MakeBrush(&brush);
+            N++;
+          }
+        }
+      }
+    } // endif AddHints==1
+    CloseFuncGroup();
+  }
+
+} // end MapBrushes
+
+//=============================================================
+void GenerateMap()
+{
+  extern void MapOut(int,int,NODE *,TRI *);
+  extern bool SingleBrushSelected;
+  int  ntri;
+
+  if(!ValidSurface()) return;
+        /*
+  ghCursorCurrent = LoadCursor(NULL,IDC_WAIT);
+  SetCursor(ghCursorCurrent);
+        */
+#if 0
+    if(SingleBrushSelected) g_FuncTable.m_pfnDeleteSelection();
+#endif
+
+  GenerateXYZ();
+  ntri = NH*NV*2;
+
+  if(Game==QUAKE3 && UsePatches != 0)
+    MapPatches();
+
+  if(Decimate > 0 && (Game != QUAKE3 || UsePatches==0) )
+  {
+    MapOut(gNumNodes,gNumTris,gNode,gTri);
+                /*
+    ghCursorCurrent = ghCursorDefault;
+    SetCursor(ghCursorCurrent);
+                */
+    return;
+  }
+
+  contents = 0;
+  // HL doesn't have detail property
+  if((Game != HALFLIFE) && UseDetail) contents += CONTENTS_DETAIL;
+  // HL and Q3 don't have ladder property
+  if((Game != HALFLIFE && Game != QUAKE3) && UseLadder) contents += CONTENTS_LADDER;
+  // Genesis requires solid property to be set explicitly
+  if(Game == GENESIS3D) contents |= CONTENTS_SOLID;
+  // Heretic 2 uses different sounds (in surface props) for different texture types
+  if(Game == HERETIC2)
+  {
+    surface[0] = GetDefSurfaceProps(Texture[Game][0]);
+    surface[1] = GetDefSurfaceProps(Texture[Game][1]);
+    surface[2] = GetDefSurfaceProps(Texture[Game][2]);
+  }
+  else
+  {
+    surface[0] = 0;
+    surface[1] = 0;
+    surface[2] = 0;
+  }
+  if(Game!=QUAKE3 || UsePatches == 0)
+    MapBrushes();
+
+        /*
+  ghCursorCurrent = ghCursorDefault;
+  SetCursor(ghCursorCurrent);
+        */
+}
+
+//=============================================================
+void GenerateXYZ()
+{
+  extern void MakeDecimatedMap(int *, int *, NODE **, TRI **);
+  double zl, zu;
+  double wh, wv;
+  int NHalfcycles;
+  double a,v,h,ha,va;
+  double delta, dr, rate;
+  double range, maxrange;
+  double r;
+  int i, j, k, N;
+  int i0, i1, j0, j1;
+  int ii, jj;
+
+//  FILE *f;
+//  char CSV[64];
+
+  if(!ValidSurface()) return;
+
+  srand(1);
+  srand(RandomSeed);
+
+  dh = (Hur-Hll)/NH;
+  dv = (Vur-Vll)/NV;
+  
+  // H & V
+  for(i=0; i<=NH; i++)
+  {
+    for(j=0;j<=NV;j++)
+    {
+      switch(Plane)
+      {
+      case PLANE_XZ0:
+      case PLANE_XZ1:
+        xyz[i][j].p[0] = Hll + i*dh;
+        xyz[i][j].p[2] = Vll + j*dv;
+        break;
+      case PLANE_YZ0:
+      case PLANE_YZ1:
+        xyz[i][j].p[1] = Hll + i*dh;
+        xyz[i][j].p[2] = Vll + j*dv;
+        break;
+      default:
+        xyz[i][j].p[0] = Hll + i*dh;
+        xyz[i][j].p[1] = Vll + j*dv;
+      }
+    }
+  }
+
+  if(WaveType == WAVE_BITMAP)
+    GenerateBitmapMapping();
+        /*
+  else if(WaveType == WAVE_FORMULA)
+    DoFormula();
+        */
+  else
+  {
+    // Initialize Z values using bilinear interpolation
+    for(i=0; i<=NH; i++)
+    {
+      zl = Z00 + i*(Z10 - Z00)/NH;
+      zu = Z01 + i*(Z11 - Z01)/NH;
+      switch(Plane)
+      {
+      case PLANE_XZ0:
+      case PLANE_XZ1:
+        for(j=0; j<=NV; j++)
+          xyz[i][j].p[1] = zl + j*(zu-zl)/NV;
+        break;
+      case PLANE_YZ0:
+      case PLANE_YZ1:
+        for(j=0; j<=NV; j++)
+          xyz[i][j].p[0] = zl + j*(zu-zl)/NV;
+        break;
+      default:
+        for(j=0; j<=NV; j++)
+          xyz[i][j].p[2] = zl + j*(zu-zl)/NV;
+      }
+    }
+  }
+  
+  switch(WaveType)
+  {
+  case WAVE_COS_SIN:
+    if(FixBorders)
+    {
+      NHalfcycles = (int)((Hur-Hll)/(WaveLength/2.));
+      NHalfcycles = max(NHalfcycles,1);
+      wh = 2.*(Hur-Hll)/NHalfcycles;
+      NHalfcycles = (int)((Vur-Vll)/(WaveLength/2.));
+      wv = 2.*(Vur-Vll)/NHalfcycles;
+      NHalfcycles = max(NHalfcycles,1);
+      i0 = 1;
+      i1 = NH-1;
+      j0 = 1;
+      j1 = NV-1;
+    }
+    else
+    {
+      wh = WaveLength;
+      wv = WaveLength;
+      i0 = 0;
+      i1 = NH;
+      j0 = 0;
+      j1 = NV;
+    }
+    
+    for(i=i0; i<=i1; i++)
+    {
+      h  = Hll + i*dh;
+      ha = ((h-Hll)/wh)*2.*PI - PI/2.;
+      for(j=j0; j<=j1; j++)
+      {
+        v  = Vll + j*dv;
+        va = ((v-Vll)/wv)*2.*PI;
+        a = Amplitude * cos( ha ) * sin( va );
+        switch(Plane)
+        {
+        case PLANE_XY1:
+          xyz[i][j].p[2] -= a;
+          break;
+        case PLANE_XZ0:
+          xyz[i][j].p[1] += a;
+          break;
+        case PLANE_XZ1:
+          xyz[i][j].p[1] -= a;
+          break;
+        case PLANE_YZ0:
+          xyz[i][j].p[0] += a;
+          break;
+        case PLANE_YZ1:
+          xyz[i][j].p[0] -= a;
+          break;
+        default:
+          xyz[i][j].p[2] += a;
+        }
+      }
+    }
+    break;
+  case WAVE_HCYLINDER:
+    for(i=0; i<=NH; i++)
+    {
+      h  = Hll + i*dh;
+      ha = ((h-Hll)/WaveLength)*2.*PI - PI/2.;
+      for(j=0; j<=NV; j++)
+      {
+        a = Amplitude * cos( ha );
+        switch(Plane)
+        {
+        case PLANE_XY1:
+          xyz[i][j].p[2] -= a;
+          break;
+        case PLANE_XZ0:
+          xyz[i][j].p[1] += a;
+          break;
+        case PLANE_XZ1:
+          xyz[i][j].p[1] -= a;
+          break;
+        case PLANE_YZ0:
+          xyz[i][j].p[0] += a;
+          break;
+        case PLANE_YZ1:
+          xyz[i][j].p[0] -= a;
+          break;
+        default:
+          xyz[i][j].p[2] += a;
+        }
+      }
+    }
+    break;
+  case WAVE_VCYLINDER:
+    for(i=0; i<=NH; i++)
+    {
+      h  = Hll + i*dh;
+      for(j=0; j<=NV; j++)
+      {
+        v  = Vll + j*dv;
+        va = ((v-Vll)/WaveLength)*2.*PI;
+        a = Amplitude * sin( va );
+        switch(Plane)
+        {
+        case PLANE_XY1:
+          xyz[i][j].p[2] -= a;
+          break;
+        case PLANE_XZ0:
+          xyz[i][j].p[1] += a;
+          break;
+        case PLANE_XZ1:
+          xyz[i][j].p[1] -= a;
+          break;
+        case PLANE_YZ0:
+          xyz[i][j].p[0] += a;
+          break;
+        case PLANE_YZ1:
+          xyz[i][j].p[0] -= a;
+          break;
+        default:
+          xyz[i][j].p[2] += a;
+        }
+      }
+    }
+    break;
+  case WAVE_ROUGH_ONLY:
+    PlasmaCloud();
+    break;
+  }
+  
+  if(WaveType != WAVE_ROUGH_ONLY)
+  {
+    // Fixed values
+    for(i=0; i<=NH; i++)
+    {
+      for(j=0; j<=NV; j++)
+      {
+        if(xyz[i][j].fixed)
+        {
+          switch(Plane)
+          {
+          case PLANE_XZ0:
+          case PLANE_XZ1:
+            xyz[i][j].p[1] = xyz[i][j].fixed_value;
+            break;
+          case PLANE_YZ0:
+          case PLANE_YZ1:
+            xyz[i][j].p[0] = xyz[i][j].fixed_value;
+            break;
+          default:
+            xyz[i][j].p[2] = xyz[i][j].fixed_value;
+          }
+          
+          if(xyz[i][j].range > 0)
+          {
+            maxrange = pow(xyz[i][j].range,2); // so we don't have to do sqrt's
+            i0 = i - (int)( floor(xyz[i][j].range/dh - 0.5) + 1 );
+            i1 = i + i - i0;
+            j0 = j - (int)( floor(xyz[i][j].range/dv - 0.5) + 1 );
+            j1 = j + j - j0;
+            if(FixBorders)
+            {
+              i0 = max(i0,1);
+              i1 = min(i1,NH-1);
+              j0 = max(j0,1);
+              j1 = min(j1,NV-1);
+            }
+            else
+            {
+              i0 = max(i0,0);
+              i1 = min(i1,NH);
+              j0 = max(j0,0);
+              j1 = min(j1,NV);
+            }
+            for(ii=i0; ii<=i1; ii++)
+            {
+              for(jj=j0; jj<=j1; jj++)
+              {
+                if(ii==i && jj==j) continue;
+                range = pow( dh*(i-ii), 2) + pow( dv*(j-jj), 2);
+                if(range > maxrange) continue;
+                dr = sqrt(range/maxrange);
+                rate = max(-30.,min(xyz[i][j].rate,30.));
+                if(rate < -1.)
+                {
+                  delta = pow((1.-dr),-rate+1.);
+                }
+                else if(rate < 0.)
+                {
+                  delta = (1+rate)*0.5*(cos(dr*PI)+1.0) -
+                    rate*pow((1.-dr),2);
+                }
+                else if(rate == 0.)
+                {
+                  delta = 0.5*(cos(dr*PI)+1.0);
+                }
+                else if(rate <= 1.)
+                {
+                  delta = (1.-rate)*0.5*(cos(dr*PI)+1.0) +
+                    rate*(1.-pow(dr,2));
+                }
+                else
+                {
+                  delta = 1.-pow(dr,rate+1);
+                }
+                switch(Plane)
+                {
+                case PLANE_XZ0:
+                case PLANE_XZ1:
+                  xyz[ii][jj].p[1] += (xyz[i][j].p[1] - xyz[ii][jj].p[1])*delta;
+                  break;
+                case PLANE_YZ0:
+                case PLANE_YZ1:
+                  xyz[ii][jj].p[0] += (xyz[i][j].p[0] - xyz[ii][jj].p[0])*delta;
+                  break;
+                default:
+                  xyz[ii][jj].p[2] += (xyz[i][j].p[2] - xyz[ii][jj].p[2])*delta;
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  if((Roughness > 0.) && (WaveType != WAVE_ROUGH_ONLY) )
+  {
+    for(i=0; i<=NH; i++)
+    {
+      for(j=0; j<=NV; j++)
+      {
+        if(CanEdit(i,j) && !xyz[i][j].fixed)
+        {
+          switch(Plane)
+          {
+          case PLANE_XZ0:
+          case PLANE_XZ1:
+            xyz[i][j].p[1] += -Roughness/2. + Roughness*((double)rand()/(double)RAND_MAX);
+            break;
+          case PLANE_YZ0:
+          case PLANE_YZ1:
+            xyz[i][j].p[0] += -Roughness/2. + Roughness*((double)rand()/(double)RAND_MAX);
+            break;
+          default:
+            xyz[i][j].p[2] += -Roughness/2. + Roughness*((double)rand()/(double)RAND_MAX);
+          }
+        }
+        else
+          r = rand();    // We still get a random number, so that fixing points
+                           // doesn't change the sequence.
+
+      }
+    }
+  }
+
+  for(i=0; i<=NH; i++)
+  {
+    for(j=0; j<=NV; j++)
+    {
+      for(k=0; k<3; k++)
+      {
+        xyz[i][j].p[k] = Nearest(xyz[i][j].p[k],2.0);
+      }
+    }
+  }
+
+  // Find minima and maxima
+  switch(Plane)
+  {
+  case PLANE_XZ0:
+  case PLANE_XZ1:
+    xmin = Hll;
+    xmax = Hur;
+    zmin = Vll;
+    zmax = Vur;
+    ymin = xyz[0][0].p[1];
+    ymax = ymin;
+    for(i=0; i<=NH; i++)
+    {
+      for(j=0; j<=NV; j++)
+      {
+        ymin = min(ymin,xyz[i][j].p[1]);
+        ymax = max(ymax,xyz[i][j].p[1]);
+      }
+    }
+    break;
+  case PLANE_YZ0:
+  case PLANE_YZ1:
+    ymin = Hll;
+    ymax = Hur;
+    zmin = Vll;
+    zmax = Vur;
+    xmin = xyz[0][0].p[0];
+    xmax = ymin;
+    for(i=0; i<=NH; i++)
+    {
+      for(j=0; j<=NV; j++)
+      {
+        xmin = min(xmin,xyz[i][j].p[0]);
+        xmax = max(xmax,xyz[i][j].p[0]);
+      }
+    }
+    break;
+    break;
+  default:
+    xmin = Hll;
+    xmax = Hur;
+    ymin = Vll;
+    ymax = Vur;
+    zmin = xyz[0][0].p[2];
+    zmax = zmin;
+    for(i=0; i<=NH; i++)
+    {
+      for(j=0; j<=NV; j++)
+      {
+        zmin = min(zmin,xyz[i][j].p[2]);
+        zmax = max(zmax,xyz[i][j].p[2]);
+      }
+    }
+  }
+  
+  xmin = Nearest(xmin,2.);
+  xmax = Nearest(xmax,2.);
+  ymin = Nearest(ymin,2.);
+  ymax = Nearest(ymax,2.);
+  zmin = Nearest(zmin,2.);
+  zmax = Nearest(zmax,2.);
+  
+  switch(Plane)
+  {
+  case PLANE_XY1:
+    backface = AtLeast(zmax+32.,32.);
+    break;
+  case PLANE_XZ0:
+    backface = NoMoreThan(ymin-32.,32.);
+    break;
+  case PLANE_XZ1:
+    backface = AtLeast(ymax+32.,32.);
+    break;
+  case PLANE_YZ0:
+    backface = NoMoreThan(xmin-32.,32.);
+    break;
+  case PLANE_YZ1:
+    backface = AtLeast(xmax+32.,32.);
+    break;
+  default:
+    backface = NoMoreThan(zmin-32.,32.);
+  }
+
+  if(gNode)
+  {
+    free(gNode);
+    free(gTri);
+    gNode = (NODE *)NULL;
+    gTri  = (TRI *)NULL;
+  }
+  if(Decimate > 0 && (Game != QUAKE3 || UsePatches==0) )
+  {
+    MakeDecimatedMap(&gNumNodes,&gNumTris,&gNode,&gTri);
+  }
+  else
+  {
+    gNumNodes = (NH+1)*(NV+1);
+    gNumTris  = NH*NV*2;
+    gNode = (NODE *) malloc(gNumNodes * sizeof(NODE));
+    gTri = (TRI *) malloc(gNumTris * sizeof(TRI));
+
+    for(i=0,N=0; i<=NH; i++)
+    {
+      for(j=0; j<=NV; j++, N++)
+      {
+        gNode[N].used = 1;
+        gNode[N].p[0] = (float)xyz[i][j].p[0];
+        gNode[N].p[1] = (float)xyz[i][j].p[1];
+        gNode[N].p[2] = (float)xyz[i][j].p[2];
+      }
+    }
+    
+    for(i=0; i<NH; i++)
+    {
+      for(j=0; j<NV; j++)
+      {
+        k = i*NV*2 + j*2;
+        if( (i+j) % 2 )
+        {
+          switch(Plane)
+          {
+          case PLANE_XY1:
+          case PLANE_XZ1:
+          case PLANE_YZ1:
+            gTri[k  ].v[0] = i*(NV+1)+j;
+            gTri[k  ].v[1] = (i+1)*(NV+1)+j+1;
+            gTri[k  ].v[2] = (i+1)*(NV+1)+j;
+            gTri[k+1].v[0] = i*(NV+1)+j;
+            gTri[k+1].v[1] = i*(NV+1)+j+1;
+            gTri[k+1].v[2] = (i+1)*(NV+1)+j+1;
+            break;
+          default:
+            gTri[k  ].v[0] = i*(NV+1)+j;
+            gTri[k  ].v[1] = (i+1)*(NV+1)+j;
+            gTri[k  ].v[2] = (i+1)*(NV+1)+j+1;
+            gTri[k+1].v[0] = i*(NV+1)+j;
+            gTri[k+1].v[1] = (i+1)*(NV+1)+j+1;
+            gTri[k+1].v[2] = i*(NV+1)+j+1;
+          }
+        }
+        else
+        {
+          switch(Plane)
+          {
+          case PLANE_XY1:
+          case PLANE_XZ1:
+          case PLANE_YZ1:
+            gTri[k  ].v[0] = i*(NV+1)+j;
+            gTri[k  ].v[1] = i*(NV+1)+j+1;
+            gTri[k  ].v[2] = (i+1)*(NV+1)+j;
+            gTri[k+1].v[0] = (i+1)*(NV+1)+j;
+            gTri[k+1].v[1] = i*(NV+1)+j+1;
+            gTri[k+1].v[2] = (i+1)*(NV+1)+j+1;
+            break;
+          default:
+            gTri[k  ].v[0] = i*(NV+1)+j;
+            gTri[k  ].v[1] = (i+1)*(NV+1)+j;
+            gTri[k  ].v[2] = i*(NV+1)+j+1;
+            gTri[k+1].v[0] = (i+1)*(NV+1)+j;
+            gTri[k+1].v[1] = (i+1)*(NV+1)+j+1;
+            gTri[k+1].v[2] = i*(NV+1)+j+1;
+          }
+        }
+      }
+    }
+  }
+/*
+  sprintf(CSV,"csv%03d.csv",Decimate);
+  f = fopen(CSV,"w");
+  for(i=0; i<gNumNodes; i++)
+  {
+    if(gNode[i].used)
+      fprintf(f,"%g,%g,%g\n",gNode[i].p[0],gNode[i].p[1],gNode[i].p[2]);
+  }
+  fclose(f);
+*/
+  for(i=0; i<gNumTris; i++)
+    PlaneFromPoints(gNode[gTri[i].v[0]].p,
+                    gNode[gTri[i].v[1]].p,
+                    gNode[gTri[i].v[2]].p,
+                    &gTri[i].plane);
+
+  // Hydra: snap-to-grid begin
+  if (SnapToGrid > 0)
+  {
+    for(i=0; i<NH; i++)
+    {
+      for(j=0; j<NV; j++)
+      {
+        switch(Plane)
+        {
+        case PLANE_XZ0:
+        case PLANE_XZ1:
+          xyz[i][j].p[1] = CalculateSnapValue(xyz[i][j].p[1]);
+          break;
+        case PLANE_YZ0:
+        case PLANE_YZ1:
+          xyz[i][j].p[0] = CalculateSnapValue(xyz[i][j].p[0]);
+          break;
+        default:
+          xyz[i][j].p[2] = CalculateSnapValue(xyz[i][j].p[2]);
+        }
+      }
+    }
+  }
+  // Hydra: snap-to-grid end
+}
+//=============================================================
+double Nearest(double x, double dx)
+{
+  double xx;
+
+  xx = (double)(floor(x/dx - 0.5)+1.)*dx;
+  if(fabs(xx) < dx/2) xx = 0.;
+  return xx;
+}
+//=============================================================
+double NoMoreThan(double x, double dx)
+{
+  double xx;
+
+  xx = (double)(floor(x/dx - 0.5)+1.)*dx;
+  if(xx > x) xx -= dx;
+  return xx;
+}
+//=============================================================
+double AtLeast(double x, double dx)
+{
+  double xx;
+
+  xx = (double)(floor(x/dx - 0.5)+1.)*dx;
+  if(xx < x) xx += dx;
+  return xx;
+}
+//=============================================================
+double LessThan(double x,double dx)
+{
+  double xx;
+
+  xx = (double)(floor(x/dx - 0.5)+1.)*dx;
+  if(xx >= x) xx -= dx;
+  return xx;
+}
+//=============================================================
+double MoreThan(double x,double dx)
+{
+  double xx;
+
+  xx = (double)(floor(x/dx - 0.5)+1.)*dx;
+  while(xx <= x)
+    xx += dx;
+  return xx;
+}
+//=============================================================
+void SubdividePlasma(int i0,int j0,int i1,int j1)
+{
+  int i, j;
+  double z1, z2;
+  double r;      // NOTE: This is used to keep the random number sequence the same
+                 //       when we fix a point. If we did NOT do this, then simply
+                 //       fixing a point at its current value would change the entire
+                 //       surface.
+
+  i = (i0+i1)/2;
+  j = (j0+j1)/2;
+  if(i1 > i0+1)
+  {
+    if(!xyz[i][j0].done)
+    {
+      xyz[i][j0].pp[2] = xyz[i0][j0].pp[2] + 
+        (xyz[i1][j0].pp[2] - xyz[i0][j0].pp[2])*(double)(i-i0)/(double)(i1-i0) +
+        ((double)(i-i0))*(-Roughness/2. + Roughness*((double)rand()/(double)RAND_MAX));
+      xyz[i][j0].done = 1;
+    }
+    else
+      r = rand();
+    if((j1 > j0) && (!xyz[i][j1].done) )
+    {
+      xyz[i][j1].pp[2] = xyz[i0][j1].pp[2] + 
+        (xyz[i1][j1].pp[2] - xyz[i0][j1].pp[2])*(double)(i-i0)/(double)(i1-i0) +
+        ((double)(i-i0))*(-Roughness/2. + Roughness*((double)rand()/(double)RAND_MAX));
+      xyz[i][j1].done = 1;
+    }
+    else
+      r = rand();
+  }
+  if(j1 > j0 + 1)
+  {
+    if(!xyz[i0][j].done)
+    {
+      xyz[i0][j].pp[2] = xyz[i0][j0].pp[2] + 
+        (xyz[i0][j1].pp[2] - xyz[i0][j0].pp[2])*(double)(j-j0)/(double)(j1-j0) +
+        ((double)(j-j0))*(-Roughness/2. + Roughness*((double)rand()/(double)RAND_MAX));
+      xyz[i0][j].done = 1;
+    }
+    else
+      r = rand();
+    if((i1 > i0) && (!xyz[i1][j].done))
+    {
+      xyz[i1][j].pp[2] = xyz[i1][j0].pp[2] + 
+        (xyz[i1][j1].pp[2] - xyz[i1][j0].pp[2])*(double)(j-j0)/(double)(j1-j0) +
+        ((double)(j-j0))*(-Roughness/2. + Roughness*((double)rand()/(double)RAND_MAX));
+      xyz[i1][j].done = 1;
+    }
+    else
+      r = rand();
+  }
+  if((i1 > i0+1) && (j1 > j0+1))
+  {
+    if(!xyz[i][j].done)
+    {
+      z1 = xyz[i0][j].pp[2] +
+        (xyz[i1][j].pp[2] - xyz[i0][j].pp[2])*(double)(i-i0)/(double)(i1-i0);
+      z2 = xyz[i][j0].pp[2] +
+        (xyz[i][j1].pp[2] - xyz[i][j0].pp[2])*(double)(j-j0)/(double)(j1-j0);
+      xyz[i][j].pp[2] = (z1+z2)/2. +
+        ((double)(i-i0))*(-Roughness/2. + Roughness*((double)rand()/(double)RAND_MAX));
+      xyz[i][j].done = 1;
+    }
+    else
+      r = rand();
+  }
+  if(i > i0+1 || j > j0+1)
+    SubdividePlasma(i0,j0,i,j);
+  if(i1 > i+1 || j > j0+1)
+    SubdividePlasma(i,j0,i1,j);
+  if(i > i0+1 || j1 > j0+1)
+    SubdividePlasma(i0,j,i,j1);
+  if(i1 > i+1 || j1 > j0+1)
+    SubdividePlasma(i,j,i1,j1);
+}
+//==================================================================================
+void PlasmaCloud()
+{
+  int i, j;
+  /* use pp[2] values until done to avoid messing with a bunch of 
+     switch statements */
+
+  for(i=0; i<=NH; i++)
+  {
+    for(j=0; j<=NV; j++)
+    {
+      if(FixedPoint(i,j))
+        xyz[i][j].done = 1;
+      else
+        xyz[i][j].done = 0;
+    }
+  }
+
+  switch(Plane)
+  {
+  case PLANE_XZ0:
+  case PLANE_XZ1:
+    for(i=0; i<=NH; i++)
+    {
+      for(j=0; j<=NV; j++)
+      {
+        if(xyz[i][j].fixed)
+          xyz[i][j].pp[2] = xyz[i][j].fixed_value;
+        else
+          xyz[i][j].pp[2] = xyz[i][j].p[1];
+      }
+    }
+    break;
+  case PLANE_YZ0:
+  case PLANE_YZ1:
+    for(i=0; i<=NH; i++)
+    {
+      for(j=0; j<=NV; j++)
+      {
+        if(xyz[i][j].fixed)
+          xyz[i][j].pp[2] = xyz[i][j].fixed_value;
+        else
+          xyz[i][j].pp[2] = xyz[i][j].p[0];
+      }
+    }
+    break;
+  default:
+    for(i=0; i<=NH; i++)
+    {
+      for(j=0; j<=NV; j++)
+      {
+        if(xyz[i][j].fixed)
+          xyz[i][j].pp[2] = xyz[i][j].fixed_value;
+        else
+          xyz[i][j].pp[2] = xyz[i][j].p[2];
+      }
+    }
+    break;
+  }
+  SubdividePlasma(0,0,NH,NV);
+  switch(Plane)
+  {
+  case PLANE_XZ0:
+  case PLANE_XZ1:
+    for(i=0; i<=NH; i++)
+    {
+      for(j=0; j<=NV; j++)
+      {
+        xyz[i][j].p[1] = xyz[i][j].pp[2];
+      }
+    }
+    break;
+  case PLANE_YZ0:
+  case PLANE_YZ1:
+    for(i=0; i<=NH; i++)
+    {
+      for(j=0; j<=NV; j++)
+      {
+        xyz[i][j].p[0] = xyz[i][j].pp[2];
+      }
+    }
+    break;
+  default:
+    for(i=0; i<=NH; i++)
+    {
+      for(j=0; j<=NV; j++)
+      {
+        xyz[i][j].p[2] = xyz[i][j].pp[2];
+      }
+    }
+    break;
+  }
+}
+//===========================================================================
+bool FixedPoint(int i, int j)
+{
+  if(xyz[i][j].fixed) return TRUE;
+  return !CanEdit(i,j);
+}
+//===========================================================================
+bool CanEdit(int i, int j)
+{
+  if(FixBorders && ( (WaveType==WAVE_COS_SIN) || (WaveType==WAVE_ROUGH_ONLY) ) )
+  {
+    if(i== 0) return FALSE;
+    if(i==NH) return FALSE;
+    if(j== 0) return FALSE;
+    if(j==NV) return FALSE;
+  }
+  if(i== 0 && j== 0) return FALSE;
+  if(i==NH && j== 0) return FALSE;
+  if(i== 0 && j==NV) return FALSE;
+  if(i==NH && j==NV) return FALSE;
+  return TRUE;
+}
+/*============================================================================
+  TriangleFromPoint
+  Determines which triangle in the gTri array bounds the input point. Doesn't
+  do anything special with border points.
+*/
+int TriangleFromPoint(double x, double y)
+{
+  int j, tri;
+
+  if(!gTri) return -1;
+
+  for(j=0, tri=-1; j<gNumTris && tri==-1; j++)
+  {
+    if( side(x,y,
+      gNode[gTri[j].v[0]].p[0],gNode[gTri[j].v[0]].p[1],
+      gNode[gTri[j].v[1]].p[0],gNode[gTri[j].v[1]].p[1]) < 0. ) continue;
+    if( side(x,y,
+      gNode[gTri[j].v[1]].p[0],gNode[gTri[j].v[1]].p[1],
+      gNode[gTri[j].v[2]].p[0],gNode[gTri[j].v[2]].p[1]) < 0. ) continue;
+    if( side(x,y,
+      gNode[gTri[j].v[2]].p[0],gNode[gTri[j].v[2]].p[1],
+      gNode[gTri[j].v[0]].p[0],gNode[gTri[j].v[0]].p[1]) < 0. ) continue;
+    tri = j;
+  }
+
+  return tri;
+}
+/*============================================================================
+  PlayerStartZ
+  Determines minimum height to place the player start such that he doesn't
+  intersect any surface brushes.
+*/
+int PlayerStartZ(double x, double y)
+{
+  int k,t[5];
+  double z, zt;
+
+  if(!gTri) return (int)zmax;
+
+  t[0] = TriangleFromPoint(x,y);
+  t[1] = TriangleFromPoint(x+PlayerBox[Game].x[0],y+PlayerBox[Game].y[0]);
+  t[2] = TriangleFromPoint(x+PlayerBox[Game].x[0],y+PlayerBox[Game].y[1]);
+  t[3] = TriangleFromPoint(x+PlayerBox[Game].x[1],y+PlayerBox[Game].y[0]);
+  t[4] = TriangleFromPoint(x+PlayerBox[Game].x[1],y+PlayerBox[Game].y[1]);
+  z = zmin;
+  for(k=0; k<5; k++)
+  {
+    zt = (gTri[t[k]].plane.dist -
+          gTri[t[k]].plane.normal[0]*x -
+          gTri[t[k]].plane.normal[1]*y   )/
+          gTri[t[k]].plane.normal[2];
+    z = max(z,zt);
+  }
+  return (int)(AtLeast(z,2.) - PlayerBox[Game].z[0]);
+}
+//=============================================================
+void XYZtoV(XYZ *xyz, vec3 *v)
+{
+  v[0][0] = (vec)Nearest(xyz->p[0],2.);
+  v[0][1] = (vec)Nearest(xyz->p[1],2.);
+  v[0][2] = (vec)Nearest(xyz->p[2],2.);
+}
+
+//=============================================================
+scene::Node* MakePatch(void)
+{
+  scene::Node* patch = Patch_AllocNode();
+#if 0
+  patch->m_patch->SetShader(Texture[Game][0]);
+#endif
+  Node_getTraversable(h_worldspawn)->insert(patch);
+  return patch;
+}
+
+//=============================================================
+void MakeBrush(BRUSH *brush)
+{
+  NodePtr node(Brush_AllocNode());
+
+#if 0
+  for(int i=0; i<brush->NumFaces; i++)
+  {
+    _QERFaceData QERFaceData;
+    if(!strncmp(brush->face[i].texture, "textures/", 9))
+      strcpy(QERFaceData.m_TextureName,brush->face[i].texture);
+    else 
+    {
+      strcpy(QERFaceData.m_TextureName,"textures/");
+      strcpy(QERFaceData.m_TextureName+9,brush->face[i].texture);  
+    }
+    QERFaceData.m_nContents = brush->face[i].Contents;
+    QERFaceData.m_nFlags    = brush->face[i].Surface;
+    QERFaceData.m_nValue    = brush->face[i].Value;
+    QERFaceData.m_fShift[0] = brush->face[i].Shift[0];
+    QERFaceData.m_fShift[1] = brush->face[i].Shift[1];
+    QERFaceData.m_fRotate   = brush->face[i].Rotate;
+    QERFaceData.m_fScale[0] = brush->face[i].Scale[0];
+    QERFaceData.m_fScale[1] = brush->face[i].Scale[1];
+    QERFaceData.m_v1[0]     = brush->face[i].v[0][0];
+    QERFaceData.m_v1[1]     = brush->face[i].v[0][1];
+    QERFaceData.m_v1[2]     = brush->face[i].v[0][2];
+    QERFaceData.m_v2[0]     = brush->face[i].v[1][0];
+    QERFaceData.m_v2[1]     = brush->face[i].v[1][1];
+    QERFaceData.m_v2[2]     = brush->face[i].v[1][2];
+    QERFaceData.m_v3[0]     = brush->face[i].v[2][0];
+    QERFaceData.m_v3[1]     = brush->face[i].v[2][1];
+    QERFaceData.m_v3[2]     = brush->face[i].v[2][2];
+    QERFaceData.m_bBPrimit  = false;
+    (g_FuncTable.m_pfnAddFaceData)(vp,&QERFaceData);
+  }
+#endif
+
+  Node_getTraversable(h_func_group)->insert(node);
+}
+//=============================================================
+void OpenFuncGroup()
+{
+  h_func_group = GlobalEntityCreator().createEntity("func_group");
+  h_func_group->IncRef();
+  if(AddTerrainKey)
+    h_func_group->m_entity->setkeyvalue("terrain", "1");
+}
+//=============================================================
+void CloseFuncGroup()
+{
+  h_func_group->DecRef();
+  if (g_FuncTable.m_pfnSysUpdateWindows != NULL)
+    g_FuncTable.m_pfnSysUpdateWindows (W_ALL);
+}