// This file is distributed under a BSD license. See LICENSE.txt for details.

#include "genminmesh.hpp"
#include "genmaterial.hpp"
#include "genbitmap.hpp"
#include "genoverlay.hpp"
#include "genblobspline.hpp"
#include "engine.hpp"

#if sLINK_LOADER
#include "_loader.hpp"
#include "_wavefront.hpp"
#endif

/****************************************************************************/
/****************************************************************************/

void GenMinVector::UnitSafe()
{
  sF64 e;
  e = x*x+y*y+z*z;
  if(e<1e-20f)
  {
    Init(1,0,0);
  }
  else
  {
    e = sFInvSqrt(e);
    x *= e;
    y *= e;
    z *= e;
  }
}

void GenMinVector::Cross(const GenMinVector &a,const GenMinVector &b)
{
  x=a.y*b.z-a.z*b.y; 
  y=a.z*b.x-a.x*b.z; 
  z=a.x*b.y-a.y*b.x;
}

sInt GenMinVector::Classify()
{
  sVector v;
  v.Init(x,y,z);
  return v.Classify();
}

/****************************************************************************/

sBool GenMinVert::IsSame(const GenMinVert &v,sBool uv)
{
  const sF32 e=0.00001f;
  if(sFAbs(Pos.x-v.Pos.x)>e) return 0;
  if(sFAbs(Pos.y-v.Pos.y)>e) return 0;
  if(sFAbs(Pos.z-v.Pos.z)>e) return 0;
  if(BoneCount!=v.BoneCount) return 0;
  if(BoneCount)
  {
    if(sCmpMem(Weights,v.Weights,BoneCount*4)!=0) return 0;
    if(sCmpMem(Matrix ,v.Matrix ,BoneCount*2)!=0) return 0;
  }
  if(uv)
  {
    if(sCmpMem(UV,v.UV,sizeof(UV))!=0) return 0;
  }
  return 1;
}

/****************************************************************************/

void GenMinMatrix::Init()
{
  BasePose.Init();
  Parent = -1;

  KeyCount = 0;
  SPtr = 0;
  RPtr = 0;
  TPtr = 0;

  Offset = 0;
  Factor = 1;
  Spread = 0;
  Spline = 0;

  NoAnimation.Init();

  Used = 0;
//  Index = -1;
}

void GenMinMatrix::Exit()
{
  sDeleteArray(SPtr);
  sDeleteArray(RPtr);
  sDeleteArray(TPtr);

  sRelease(Spline);
}

/****************************************************************************/

GenMinMeshAnim::GenMinMeshAnim(sInt matrixCount)
{
  Matrices.Init(matrixCount);
  Matrices.Resize(matrixCount);

  for(sInt i=0;i<matrixCount;i++)
    Matrices[i].Init();
}

GenMinMeshAnim::~GenMinMeshAnim()
{
  for(sInt i=0;i<Matrices.Count;i++)
    Matrices[i].Exit();

  Matrices.Exit();
}

sInt GenMinMeshAnim::GetMatrixCount()
{
  return Matrices.Count;
}

GenMinMeshAnim *GenMinMeshAnim::Copy()
{
  GenMinMeshAnim *dest;
  GenMinMatrix *d,*s;

  sInt count = Matrices.Count;
  dest = new GenMinMeshAnim(count);
  for(sInt i=0;i<count;i++)
  {
    s = &Matrices[i];
    d = &dest->Matrices[i];

    d->Init();
    d->BasePose = s->BasePose;
    d->Parent = s->Parent;
    d->Factor = s->Factor;
    d->Spread = s->Spread;
    d->Offset = s->Offset;
    d->KeyCount = s->KeyCount;
    for(sInt j=0;j<9;j++)
      d->SRT[j] = s->SRT[j];
    if(s->SPtr)
    {
      d->SPtr = new sF32[s->KeyCount*3];
      sCopyMem(d->SPtr,s->SPtr,s->KeyCount*3*4);
    }
    if(s->RPtr)
    {
      d->RPtr = new sF32[s->KeyCount*3];
      sCopyMem(d->RPtr,s->RPtr,s->KeyCount*3*4);
    }
    if(s->TPtr)
    {
      d->TPtr = new sF32[s->KeyCount*3];
      sCopyMem(d->TPtr,s->TPtr,s->KeyCount*3*4);
    }

    if(s->Spline)
    {
      d->Spline = s->Spline;
      d->Spline->AddRef();
    }

    d->NoAnimation = s->NoAnimation;
  }
  return dest;
}


void GenMinMeshAnim::EvalAnimation(sF32 time,sF32 metamorph,sMatrix *matrices)
{
  GenMinMatrix *mp;
  sMatrix mat;
  sF32 srt[9];

  if(time<0)
    time = 0;

  for(sInt i=0;i<Matrices.Count;i++)
  {
    mp = &Matrices.Array[i];
    // get animated matrix
    if(mp->KeyCount)
    {
      sInt k0,k1;
      sF32 f;
      sF32 v0,v1;

      k0 = (sInt)(mp->KeyCount*time*1024);
      f = (k0 & 1023)/1024.0f;
      k0 = (k0/1024) % mp->KeyCount;
      k1 = (k0+1) % mp->KeyCount;

      for(sInt i=0;i<9;i++)
        srt[i] = mp->SRT[i];

      for(sInt i=0;i<3;i++)
      {
        if((&mp->SPtr)[i])
        {
          for(sInt j=0;j<3;j++)
          {
            v0 = (&mp->SPtr)[i][k0*3+j];
            v1 = (&mp->SPtr)[i][k1*3+j];
            srt[i*3+j] = v0 + (v1-v0)*f;
          }
        }
      }

      //srt[0] = srt[1] = srt[2] = 1.0f; // HACK HACK HACK

      mat.InitSRT(srt);
    }
    else if(mp->Spline)
    {
      sF32 dummy;
      sF32 scalef = (Matrices.Count > 1) ? 1.0f / (Matrices.Count - 1) : 0.0f;

      mp->Spline->Eval(time*mp->Factor+mp->Offset+i*mp->Spread*scalef,time,mat,dummy);
    }
    else
    {
      mat = mp->NoAnimation;
    }

    // concatenate matrices
    if(mp->Parent>=0)
      mp->Temp.MulA(mat,Matrices[mp->Parent].Temp);
    else
      mp->Temp = mat;

    matrices[i].MulA(mp->BasePose,mp->Temp);
  }
}

/****************************************************************************/
/****************************************************************************/

sU32 GenMinMesh::HashVector(const GenMinVector &v)
{
  const sU8 *src = (const sU8 *) &v.x;
  sU32 hash = 2166136261;
  for(sInt i=0;i<sizeof(GenMinVector);i++)
    hash = (hash ^ *src++) * 16777619;

  return hash;
}

/****************************************************************************/

GenMinMesh::GenMinMesh()
{
  ClassId = KC_MINMESH;
  Vertices.Init();
  Faces.Init();
  Clusters.Init();

  Clusters.Add()->Init(0);    // deleted cluster
  AddCluster(0,0);            // default cluster
  Animation = 0;
  Stripped = sFALSE;
  CompletelyRigid = sFALSE;
  NormalsOK = 0;
  ChangeFlag = 3;
  PreparedMesh = 0;
#if !sPLAYER
  WireMesh = 0;
#endif
}

GenMinMesh::~GenMinMesh()
{
  for(sInt i=0;i<Clusters.Count;i++)
    if(Clusters[i].Mtrl)
      Clusters[i].Mtrl->Release();
  sRelease(Animation);

  Vertices.Exit();
  Faces.Exit();
  Clusters.Exit();

  UnPrepare();
#if !sPLAYER
  UnPrepareWire();
#endif
}

void GenMinMesh::Copy(KObject *o)
{
  GenMinMesh *mm;
  sVERIFY(o->ClassId==KC_MINMESH);

  mm = (GenMinMesh *) o;

  for(sInt i=0;i<Clusters.Count;i++)
    if(Clusters[i].Mtrl)
      Clusters[i].Mtrl->Release();
  Clusters.Count = 0;

  if(Animation)
    Animation->Release();

  Vertices.Copy(mm->Vertices);
  Faces.Copy(mm->Faces);
  Clusters.Copy(mm->Clusters);
  if(mm->Animation)
    Animation = mm->Animation->Copy();

  for(sInt i=0;i<Clusters.Count;i++)
    if(Clusters[i].Mtrl)
      Clusters[i].Mtrl->AddRef();

  CompletelyRigid = mm->CompletelyRigid;

  ChangeTopo();
}

void GenMinMesh::Clear()
{
  Vertices.Count = 0;
  Faces.Count = 0;
  ChangeTopo();
}

/****************************************************************************/

void GenMinMesh::ChangeGeo()
{
  NormalsOK = 0;
}

void GenMinMesh::ChangeTopo()
{
  NormalsOK = 0;
}

/****************************************************************************/

sInt GenMinMesh::AddCluster(GenMaterial *mtrl,sInt pass,sInt id,sInt anim,sInt mtx)
{
  if(mtrl==0)
    mtrl = GenOverlayManager->DefaultMat;

  for(sInt i=1;i<Clusters.Count;i++)
    if(Clusters[i].Mtrl == mtrl && Clusters[i].Id == id
      && Clusters[i].AnimType == anim && Clusters[i].AnimMatrix == mtx)
      return i;

  sInt result = Clusters.Count;
  Clusters.Add()->Init(mtrl,pass,id,anim,mtx);
  mtrl->AddRef();
  return result;
}

void GenMinMesh::CreateAnimation(sInt matrixCount)
{
  sRelease(Animation);
  Animation = new GenMinMeshAnim(matrixCount);
}

sInt GenMinMesh::OppositeEdge(sInt edgeTag) const
{
  return Faces[edgeTag >> 3].Adjacent[edgeTag & 7];
}

sInt GenMinMesh::NextFaceEdge(sInt edgeTag) const
{
  const GenMinFace *face = &Faces[edgeTag >> 3];
  if((edgeTag & 7) == face->Count - 1)
    return edgeTag & ~7;
  else
    return edgeTag + 1;
}

sInt GenMinMesh::PrevFaceEdge(sInt edgeTag) const
{
  const GenMinFace *face = &Faces[edgeTag >> 3];
  if((edgeTag & 7) == 0)
    return edgeTag + face->Count - 1;
  else
    return edgeTag - 1;
}

sInt GenMinMesh::NextVertEdge(sInt edgeTag) const
{
  return OppositeEdge(PrevFaceEdge(edgeTag));
}

sInt GenMinMesh::PrevVertEdge(sInt edgeTag) const
{
  return NextFaceEdge(OppositeEdge(edgeTag));
}

sInt GenMinMesh::GetVertexId(sInt edgeTag) const
{
  return Faces[edgeTag >> 3].Vertices[edgeTag & 7];
}

void GenMinMesh::EdgeFlip(sInt e0)
{
  sInt e1 = OppositeEdge(e0);
  sVERIFY(e1 != -1); // may only flip internal edges

  sInt f0n = e0 >> 3;
  sInt f1n = e1 >> 3;
  e0 &= 7;
  e1 &= 7;
  sVERIFY(e0 < 3 && e1 < 3);

  GenMinFace *f0 = &Faces[f0n];
  GenMinFace *f1 = &Faces[f1n];
  sVERIFY(f0->Count == 3 && f1->Count == 3); // may only flip triangles

  static const sInt wrap[5] = { 0,1,2,0,1 }; // wrap[n]=n%3

  // get vertices
  sInt va = f0->Vertices[e0];
  sInt vb = f1->Vertices[wrap[e1+2]];
  sInt vc = f1->Vertices[e1];
  sInt vd = f0->Vertices[wrap[e0+2]];

  // outer edges
  sInt oab = f1->Adjacent[wrap[e1+1]];
  sInt obc = f1->Adjacent[wrap[e1+2]];
  sInt ocd = f0->Adjacent[wrap[e0+1]];
  sInt oda = f0->Adjacent[wrap[e0+2]];

  // gen output triangles
  f0->Vertices[0] = vb;
  f0->Vertices[1] = vd;
  f0->Vertices[2] = va;
  f1->Vertices[0] = vd;
  f1->Vertices[1] = vb;
  f1->Vertices[2] = vc;

  // gen output adjacency
  f0->Adjacent[0] = f1n<<3;
  f0->Adjacent[1] = oda;
  f0->Adjacent[2] = oab;
  f1->Adjacent[0] = f0n<<3;
  f1->Adjacent[1] = obc;
  f1->Adjacent[2] = ocd;

  // update outer adjacency
  if(oab!=-1) Faces[oab>>3].Adjacent[oab&7] = (f0n<<3)|2;
  if(obc!=-1) Faces[obc>>3].Adjacent[obc&7] = (f1n<<3)|1;
  if(ocd!=-1) Faces[ocd>>3].Adjacent[ocd&7] = (f1n<<2)|2;
  if(oda!=-1) Faces[oda>>3].Adjacent[oda&7] = (f0n<<3)|1;
}

/****************************************************************************/

sU8 *GenMinMesh::ExportToBlob(sInt &size) const
{
  // determine how much space we need. layout:
  //
  // # vertices  (4b)
  // per vertex:
  //   bonecount (1b)
  //   color     (4b)
  //   pos       (12b)
  //   uv0       (8b)   we don't save uv1!
  //   per bone: (bonecount times)
  //     weight   (2b as f16)
  //     matrix   (2b)
  //
  // # faces     (4b)
  // per face:
  //   count     (1b)
  //   cluster   (1b)
  //   per vertex: (count times)
  //     index   (2b/4b)
  //
  // # clusters  (4b)
  // per cluster:
  //   pass/animtype (4b)
  //   id        (4b)
  //   matrix    (4b)
  
  sInt nTotalBones = 0,nVertexRefs = 0;
  sBool vert16Bit = Vertices.Count < 65536;

  for(sInt i=0;i<Vertices.Count;i++)
    nTotalBones += Vertices[i].BoneCount;

  for(sInt i=0;i<Faces.Count;i++)
    nVertexRefs += Faces[i].Count;

  size = 4 + 25*Vertices.Count + 4*nTotalBones
       + 4 + 2*Faces.Count + (vert16Bit ? 2 : 4)*nVertexRefs
       + 4 + 12*Clusters.Count;

  // allocate
  sU8 *dataStart = new sU8[size];
  sU8 *data = dataStart;

  // save vertices
  *(sU32 *) data = Vertices.Count; data += 4;
  for(sInt i=0;i<Vertices.Count;i++)
  {
    const GenMinVert *vert = &Vertices[i];

    *data++ = vert->BoneCount;
    *(sU32 *) data = vert->Color; data += 4;
    *(sF32 *) data = vert->Pos.x; data += 4;
    *(sF32 *) data = vert->Pos.y; data += 4;
    *(sF32 *) data = vert->Pos.z; data += 4;
    *(sF32 *) data = vert->UV[0][0]; data += 4;
    *(sF32 *) data = vert->UV[0][1]; data += 4;

    for(sInt j=0;j<vert->BoneCount;j++)
    {
      sWriteF16(data,vert->Weights[j]);
      *(sU16 *) data = vert->Matrix[j]; data += 2;
    }
  }

  // save faces
  *(sU32 *) data = Faces.Count; data += 4;
  for(sInt i=0;i<Faces.Count;i++)
  {
    const GenMinFace *face = &Faces[i];

    *data++ = face->Count;
    *data++ = face->Cluster;

    if(vert16Bit)
    {
      for(sInt j=0;j<face->Count;j++)
      {
        *(sU16 *) data = face->Vertices[j]; data += 2;
      }
    }
    else
    {
      for(sInt j=0;j<face->Count;j++)
      {
        *(sU32 *) data = face->Vertices[j]; data += 4;
      }
    }
  }

  // save clusters
  *(sU32 *) data = Clusters.Count; data += 4;
  for(sInt i=0;i<Clusters.Count;i++)
  {
    const GenMinCluster *cluster = &Clusters[i];

    *(sU32 *) data = cluster->RenderPass | (cluster->AnimType << 24); data += 4;
    *(sU32 *) data = cluster->Id; data += 4;
    *(sU32 *) data = cluster->AnimMatrix; data += 4;
  }

  // return (we may write slightly less than anticipated due to optimizations
  // in sWriteF16)
  sVERIFY(data <= &dataStart[size]);
  size = data - dataStart;

  return dataStart;
}

void GenMinMesh::ImportFromBlob(const sU8 *data)
{
  // read vertices
  sInt nVerts = *(sU32 *) data; data += 4;
  sBool vert16Bit = nVerts < 65536;
  Vertices.Resize(nVerts);

  sSetMem(&Vertices[0],0,nVerts * sizeof(GenMinVert));

  for(sInt i=0;i<nVerts;i++)
  {
    GenMinVert *vert = &Vertices[i];

    vert->BoneCount = *data++;
    vert->Color = *(sU32 *) data; data += 4;
    vert->Pos.x = *(sF32 *) data; data += 4;
    vert->Pos.y = *(sF32 *) data; data += 4;
    vert->Pos.z = *(sF32 *) data; data += 4;
    vert->UV[0][0] = *(sF32 *) data; data += 4;
    vert->UV[0][1] = *(sF32 *) data; data += 4;

    for(sInt j=0;j<vert->BoneCount;j++)
    {
      vert->Weights[j] = sReadF16(data);
      vert->Matrix[j] = *(sU16 *) data; data += 2;
    }
  }

  // read faces
  sInt nFaces = *(sU32 *) data; data += 4;
  Faces.Resize(nFaces);

  sSetMem(&Faces[0],0,nFaces * sizeof(GenMinFace));

  for(sInt i=0;i<nFaces;i++)
  {
    GenMinFace *face = &Faces[i];

    face->Count = *data++;
    face->Cluster = *data++;

    if(vert16Bit)
    {
      for(sInt j=0;j<face->Count;j++)
      {
        face->Vertices[j] = *(sU16 *) data; data += 2;
      }
    }
    else
    {
      for(sInt j=0;j<face->Count;j++)
      {
        face->Vertices[j] = *(sU32 *) data; data += 4;
      }
    }
  }

  // free existing clusters
  for(sInt i=0;i<Clusters.Count;i++)
    sRelease(Clusters[i].Mtrl);

  // read clusters
  sInt nClusters = *(sU32 *) data; data += 4;
  Clusters.Resize(nClusters);

  sSetMem(&Clusters[0],0,nClusters * sizeof(GenMinCluster));

  for(sInt i=0;i<nClusters;i++)
  {
    GenMinCluster *cluster = &Clusters[i];

    if(i) // every cluster except the first one gets the default material
    {
      cluster->Mtrl = GenOverlayManager->DefaultMat;
      cluster->Mtrl->AddRef();
    }

    sU32 passAnim = *(sU32 *) data; data += 4;
    cluster->RenderPass = passAnim & 0xffffff;
    cluster->AnimType = passAnim >> 24;
    cluster->Id = *(sU32 *) data; data += 4;
    cluster->AnimMatrix = *(sU32 *) data; data += 4;
  }

  // that should be everything!
}

/****************************************************************************/

// flags:
//
// 0x01  close x
// 0x02  close y
// 0x04  stitch x
// 0x08  stitch y
// 0x10  invert grid
// 0x20  use cluster 0, making the grid invisible/deleted

GenMinVert *GenMinMesh::MakeGrid(sInt tx,sInt ty,sInt flags)
{
  sInt x,y;
  sInt vi,fi,vc,fc,va,fa,bits;
  GenMinVert *vp;
  GenMinFace *fp;
  sInt cluster;

  // additions (top/bottom)


  bits = 0;
  if(flags & 1) { bits++; }
  if(flags & 2) { bits++; }
  fa = tx*bits;
  va = bits;
  if(flags & 4) { fa+=tx+1; }
  if(flags & 8) { fa+=ty+1; }
  cluster = (flags&32)?0:1;


  // vertices

  vc = (tx+1)*(ty+1);
  vi = Vertices.Count;
  Vertices.SetMax(vi+vc+va);
  vp = &Vertices[vi];
  Vertices.Count = vi+vc+va;
  sSetMem(vp,0,sizeof(*vp)*(vc+va));

  for(y=0;y<=ty;y++)
  {
    for(x=0;x<=tx;x++)
    {
      vp->Pos.x = (1.0f*x/tx)-0.5f;
      vp->Pos.y = (1.0f*y/ty)-0.5f;
      vp->UV[0][0] = 1.0f*x/tx;
      vp->UV[0][1] = 1.0f-1.0f*(y)/(ty);
      vp->UV[1][0] = 1.0f*((x==tx)?0:x)/tx;
      vp->UV[1][1] = 1.0f-1.0f*((y==ty)?0:y)/(ty);
      vp->Select = 1;
      vp->Color = 0;//~0;
      vp++;
    }
  }

  // faces

  fc = tx*ty;
  fi = Faces.Count;
  Faces.SetMax(fi+fc+fa);
  fp = &Faces[fi];
  Faces.Count = fi+fc+fa;
  sSetMem(fp,0,sizeof(*fp)*(fc+fa));

  for(y=0;y<ty;y++)
  {
    for(x=0;x<tx;x++)
    {
      fp->Select = 1;
      fp->Count = 4;
      fp->Cluster = cluster;
      fp->Vertices[0] = vi+(y+0)*(tx+1)+(x+0);
      fp->Vertices[1] = vi+(y+1)*(tx+1)+(x+0);
      fp->Vertices[2] = vi+(y+1)*(tx+1)+(x+1);
      fp->Vertices[3] = vi+(y+0)*(tx+1)+(x+1);
      if(flags & 16)
      {
        sSwap(fp->Vertices[0],fp->Vertices[3]);
        sSwap(fp->Vertices[1],fp->Vertices[2]);
      }
      fp++;
    }
  }

  // additions (top / bottom)

  sInt center = vi+vc;
  sInt border = vi; 
  for(sInt i=0;i<2;i++)     // note that i is 0 or 1
  {
    if(flags & (i+1))
    {
      vp->UV[0][0] = 1.0f;
      vp->UV[0][1] = 1-i;
      vp->Select = 0;
      vp->Color = 0;//~0;
      vp++;
      for(x=0;x<tx;x++)
      {
        fp->Select = 1;
        fp->Count = 3;
        fp->Cluster = cluster;
        fp->Vertices[0] = center;
        fp->Vertices[1] = border+i;
        border++;
        fp->Vertices[2] = border-i;
        fp++;
      }
    }
    border = vi+(ty)*(tx+1);
    if(flags&1) center++;
  }

  // additions: stitches

  if(flags&4)
  {
    for(sInt i=0;i<=tx;i++)
    {
      fp->Count = 2;
      fp->Vertices[0] = vi+i;
      fp->Vertices[1] = vi+i+ty*(tx+1);
      fp++;
    }
  }

  if(flags&8)
  {
    for(sInt i=0;i<=ty;i++)
    {
      fp->Count = 2;
      fp->Vertices[0] = vi+i*(tx+1);
      fp->Vertices[1] = vi+i*(tx+1)+tx;
      fp++;
    }
  }

  return &Vertices[vi];
}

void GenMinMesh::Transform(sInt sel,const sMatrix &mat,sInt src,sInt dest,sInt op)
{
  sVector v;
  GenMinVert *vp;
  sBool ok;

  vp = Vertices.Array;
  src--;
  dest--;
  sVERIFY(src<KMM_MAXUV);
  sVERIFY(dest<KMM_MAXUV);
  for(sInt i=0;i<Vertices.Count;i++)
  {
    ok = 1;
    if(sel==MMU_SELECTED)
      ok = vp->Select;
    else if(sel==MMU_UNSELECTED)
      ok = !vp->Select;

    if(ok)
    {
      if(src<0)
        vp->Pos.Out(v,1);
      else
        v.Init(vp->UV[src][0],vp->UV[src][1],0,1);
     
      v.Rotate34(mat);
      if(op==1)             // normalize
      {
        v.Unit3();
      }
      else if(op==2)        // polar mapping
      {
        sF32 u_,v_;
        u_ = sFATan2(v.x,v.z)/sPI2F+0.5f;
        v_ = 0.5f-sFATan2(v.y,sFSqrt(v.x*v.x+v.z*v.z))/sPIF;
        v.Init(u_,v_,0,0);
      }

      if(dest<0)
      {
        vp->Pos.Init(v);
      }
      else
      {
        vp->UV[dest][0] = v.x;
        vp->UV[dest][1] = v.y;
      }
    }
    vp++;
  }
}

void GenMinMesh::CalcNormals()
{
  GenMinFace *mf;
  GenMinVert *mv;
  sInt i,j;
  GenMinVector n,t;
  GenMinVector d0,d1;
  sInt p0,p1,p2;

  if(NormalsOK)
    return;
  NormalsOK = sTRUE;

  mf = &Faces[0];
  mv = &Vertices[0];

  for(i=0;i<Vertices.Count;i++)
  {
    mv[i].Normal.Init();
    mv[i].Tangent.Init();
  }
  for(i=0;i<Faces.Count;i++)
  {
    n.Init();
    if(mf[i].Count==2)
    {
      p0 = mf[i].Vertices[0];
      p1 = mf[i].Vertices[1];
      mv[p0].Normal.Add(mv[p1].Normal);
      mv[p1].Normal = mv[p0].Normal;
      mv[p0].Tangent.Add(mv[p1].Tangent);
      mv[p1].Tangent = mv[p0].Tangent;
    }
    else if(mf[i].Count>=3)
    {
      // normal
      p0 = mf[i].Vertices[0];
      p1 = mf[i].Vertices[1];
      p2 = mf[i].Vertices[2];
      d0.Sub(mv[p1].Pos,mv[p0].Pos);
      d1.Sub(mv[p2].Pos,mv[p0].Pos);
      n.Cross(d0,d1);
      n.UnitSafe();
      mf[i].Normal = n;

      // tangent
      sF32 b1 = mv[p1].UV[0][0] - mv[p0].UV[0][0];
      sF32 b2 = mv[p2].UV[0][0] - mv[p0].UV[0][0];
      sF32 c1 = mv[p1].UV[0][1] - mv[p0].UV[0][1];
      sF32 c2 = mv[p2].UV[0][1] - mv[p0].UV[0][1];
      sF32 ix = b1 * c2 - c1 * b2;

      t.Init(0,0,0);
      if(sFAbs(ix) > 1e-20f)
      {
        ix = 1.0f / ix;
        t.AddScale(d0,c2 * ix);
        t.AddScale(d1,-c1 * ix);
      }

      // orthogonalize
      t.AddScale(n,-t.Dot(n));
      t.UnitSafe();

      // accumulate
      for(j=0;j<mf[i].Count;j++)
      {
        sInt v = mf[i].Vertices[j];
        mv[v].Normal.Add(n);
        mv[v].Tangent.Add(t);
      }
    }
  }
  for(i=0;i<Vertices.Count;i++)
  {
    mv[i].Normal.UnitSafe();
    mv[i].Tangent.UnitSafe();
  }
}

/*
void GenMinMesh::Cleanup()
{
  sInt *temp = new sInt[Vertices.Count];

  for(sInt i=0;i<Vertices.Count;i++)
    temp[i] = 0;

  // delete faces and mark used vertices

  sInt fc = 0;
  for(sInt i=0;i<Faces.Count;i++)
  {
    GenMinFace *face = &Faces[i];

    if(face->Cluster && face->Count==3)     // skip double vertices (like a quad that is actually a tri)
    {
      sInt last = face->Vertices[face->Count-1];  
      sInt pc = 0;                          // quads that are actually a line will have two vertices after this.
      for(sInt j=0;j<face->Count;j++)
      {
        if(face->Vertices[j]!=last)
          last = face->Vertices[pc++] = face->Vertices[j];
      }
      face->Count = (pc>=3)?pc:0;
    }
      

    if(face->Count>0)                    // skip degenerated faces (like generated by the previous pass)
    {
      for(sInt j=0;j<face->Count;j++)  // mark used vertices
        temp[face->Vertices[j]] = 1;

      Faces[fc++] = Faces[i];            // copy face.
    }
  }
  Faces.Count = fc;

  // remap vertices

  sInt vc = 0;                          
  for(sInt i=0;i<Vertices.Count;i++)
  {
    if(temp[i])
    {
      Vertices[vc] = Vertices[i];         // copy the vertex
      temp[i] = vc++;                     // update remapping
    }
    else
    {
      temp[i] = -1;
    }
  }
  Vertices.Count = vc;

  // remap faces

  for(sInt i=0;i<Faces.Count;i++)
  {
    GenMinFace *face = &Faces[i];
    for(sInt j=0;j<face->Count;j++)
    {
      face->Vertices[j] = temp[face->Vertices[j]];
      sVERIFY(face->Vertices[j]>=0);
    }
  }

  // done

  delete[] temp;
}
*/

void GenMinMesh::Add(GenMinMesh *other)
{
  sInt p,a;
  

  // copy faces

  p = Faces.Count;
  a = other->Faces.Count;
  Faces.Resize(p+a);
  sCopyMem(&Faces[p],&other->Faces[0],a*sizeof(GenMinFace));

  // update vertex and cluster in faces

  for(sInt i=p;i<Faces.Count;i++)
  {
    GenMinFace *f = &Faces[i];
    for(sInt j=0;j<f->Count;j++)
      f->Vertices[j] += Vertices.Count;
    if(f->Cluster>0)
      f->Cluster += Clusters.Count-1;
  }

  // copy vertices

  p = Vertices.Count;
  a = other->Vertices.Count;
  Vertices.Resize(p+a);
  sCopyMem(&Vertices[p],&other->Vertices[0],a*sizeof(GenMinVert));

  // copy and refcount clusters. clusters should get merged...

  p = Clusters.Count;
  a = other->Clusters.Count-1;
  Clusters.Resize(p+a);
  sCopyMem(&Clusters[p],&other->Clusters[1],a*sizeof(GenMinCluster));
  for(sInt i=p;i<Clusters.Count;i++)
    if(Clusters[i].Mtrl)
      Clusters[i].Mtrl->AddRef();

  ChangeTopo();
}

void GenMinMesh::MergeClusters()
{
  sInt *rev;
  sInt revcount;

  for(sInt i=0;i<Clusters.Count;i++)
    Clusters[i].Temp = -1;

  Clusters[0].Temp = 0;
  for(sInt i=0;i<Faces.Count;i++)
    Clusters[Faces[i].Cluster].Temp = 0;

  rev = new sInt[Clusters.Count];
  revcount = 0;

  for(sInt i=0;i<Clusters.Count;i++)
  {
    if(Clusters[i].Temp>=0)
    {
      for(sInt j=0;j<revcount;j++)
      {
        if(Clusters[i].Equals(Clusters[rev[j]]))
        {
          Clusters[i].Temp = Clusters[rev[j]].Temp;
          goto next;
        }
      }
      rev[revcount] = i;
      Clusters[i].Temp = revcount;
      revcount++;
next:;
    }
  }

  for(sInt i=0;i<Faces.Count;i++)
  {
    sVERIFY(Clusters[Faces[i].Cluster].Temp>=0);
    Faces[i].Cluster = Clusters[Faces[i].Cluster].Temp;
  }

  for(sInt i=0;i<revcount;i++)
    if(Clusters[rev[i]].Mtrl)
      Clusters[rev[i]].Mtrl->AddRef();
  for(sInt i=0;i<Clusters.Count;i++)
    if(Clusters[i].Mtrl)
      Clusters[i].Mtrl->Release();
  sVERIFY(revcount>0);
  sVERIFY(rev[0]==0);
  for(sInt i=0;i<revcount;i++)
  {
    sVERIFY(i<=rev[i]);
    Clusters[i] = Clusters[rev[i]];
  }
  Clusters.Count = revcount;

  delete[] rev;
}

void GenMinMesh::Invert()
{
  for(sInt i=0;i<Faces.Count;i++)
  {
    sInt buffer[KMM_MAXVERT];
    GenMinFace *face = &Faces[i];
    sCopyMem(buffer,face->Vertices,face->Count*4);
    for(sInt j=0;j<face->Count;j++)
      face->Vertices[j] = buffer[face->Count-1-j];
  }

  ChangeTopo();
}

void GenMinMesh::CalcBBox(sAABox &box) const
{
  box.Min.Init( 1e+15f,  1e+15f,  1e+15f, 1.0f);
  box.Max.Init(-1e+15f, -1e+15f, -1e+15f, 1.0f);

  for(sInt i=0;i<Faces.Count;i++)
  {
    const GenMinFace *face = &Faces[i];

    for(sInt j=0;j<face->Count;j++)
    {
      const GenMinVert *vert = &Vertices[face->Vertices[j]];
      box.Min.x = sMin(box.Min.x,vert->Pos.x);
      box.Min.y = sMin(box.Min.y,vert->Pos.y);
      box.Min.z = sMin(box.Min.z,vert->Pos.z);
      box.Max.x = sMax(box.Max.x,vert->Pos.x);
      box.Max.y = sMax(box.Max.y,vert->Pos.y);
      box.Max.z = sMax(box.Max.z,vert->Pos.z);
    }
  }
}

void GenMinMesh::SelectAll(sInt in,sInt out)
{
  GenMinVert *vp;
  sInt ok;

  vp = Vertices.Array;
  for(sInt i=0;i<Vertices.Count;i++)
  {
    ok = (in==MMU_ALL || (in==MMU_SELECTED&&vp->Select) || (in==MMU_UNSELECTED&&!vp->Select));

    switch(out)
    {
    case MMS_ADD:
      if(ok) vp->Select = 1;
      break;
    case MMS_SUB:
      if(ok) vp->Select = 0;
      break;
    case MMS_SET:
      vp->Select = ok;
      break;
    case MMS_SETNOT:
      vp->Select = !ok;
      break;
    }
    vp++;
  }
}

/****************************************************************************/

sInt *GenMinMesh::CalcMergeVerts() const
{

/*
// this code does nothing. use it when you think this function does not work
  sInt *remap = new sInt[Vertices.Count];
  for(sInt i=0;i<Vertices.Count;i++)
    remap[i] = i;
  return remap;
*/
  sInt *next = new sInt[Vertices.Count];
  sInt *remap = new sInt[Vertices.Count];
  sInt hashSize = 1024;

  while(Vertices.Count > hashSize * 8)
    hashSize *= 2;

  // alloc+clear hash table
  sInt *hash = new sInt[hashSize];
  sSetMem(hash,0xff,hashSize*sizeof(sInt));

  // find vertices with identical position
  for(sInt i=0;i<Vertices.Count;i++)
  {
    // calc bucket
    const GenMinVert *vert = &Vertices[i];
    sU32 bucket = HashVector(vert->Pos) & (hashSize - 1);

    // search for first matching vertex
    sInt first;
    for(first = hash[bucket]; first != -1; first = next[first])
    {
      const GenMinVert *cmp = &Vertices[first];

      if(1/*cmp->MergeTag == vert->MergeTag*/
        && !sCmpMem(&cmp->Pos,&vert->Pos,sizeof(vert->Pos))
        && !sCmpMem(&cmp->Matrix,&vert->Matrix,sizeof(vert->Matrix))
        && !sCmpMem(&cmp->Weights,&vert->Weights,sizeof(vert->Weights)))
      {
        remap[i] = first;
        break;
      }
    }

    // append to hash list if it's the first at that position
    if(first == -1)
    {
      remap[i] = i;
      next[i] = hash[bucket];
      hash[bucket] = i;
    }
  }

  delete[] hash;
  delete[] next;
  return remap;
}

/****************************************************************************/

struct GenMinTempEdge
{
  sInt FaceVert;
  sInt v0,v1;     // v0 < v1!

  bool operator < (const GenMinTempEdge &b) const
  {
    return v0 < b.v0 || v0 == b.v0 && v1 < b.v1;
  }
};

static void SiftDownEdge(GenMinTempEdge *list,sInt n,sInt k)
{
  GenMinTempEdge v = list[k];

  while(k < (n >> 1))
  {
    sInt j = k*2+1;
    if(j+1 < n && list[j] < list[j+1])
      j++;

    if(!(v < list[j]))
      break;

    list[k] = list[j];
    k = j;
  }

  list[k] = v;
}

static void HeapSortEdges(GenMinTempEdge *list,sInt n)
{
  for(sInt k=(n >> 1)-1;k>=0;k--)
    SiftDownEdge(list,n,k);

  while(--n > 0)
  {
    sSwap(list[0],list[n]);
    SiftDownEdge(list,n,0);
  }
}

static void ConnectFaces(GenMinFace *faces,const sInt *buf,sInt count)
{
  sVERIFY(count <= 2);

  if(count == 1) // boundary
    faces[*buf >> 3].Adjacent[*buf & 7] = -1;
  else if(count == 2) // inner (shared) edge
  {
    sInt f0 = buf[0], f1 = buf[1];
    faces[f0 >> 3].Adjacent[f0 & 7] = f1;
    faces[f1 >> 3].Adjacent[f1 & 7] = f0;
  }
}

sBool GenMinMesh::CalcAdjacencyCore(const sInt *remap)
{
  // calculate number of edges we need
  sInt numEdges = 0;
  for(sInt i=0;i<Faces.Count;i++)
    numEdges += Faces[i].Count >= 3 ? Faces[i].Count : 0;

  // make edge list
  GenMinTempEdge *edges = new GenMinTempEdge[numEdges], *edgePtr = edges;
  for(sInt i=0;i<Faces.Count;i++)
  {
    GenMinFace *face = &Faces[i];
    if(face->Count < 3)
      continue;

    for(sInt j=0;j<face->Count;j++)
    {
      edgePtr->FaceVert = i * 8 + j;
      edgePtr->v0 = remap[face->Vertices[j]];
      edgePtr->v1 = remap[face->Vertices[(j + 1 == face->Count) ? 0 : j+1]];
      if(edgePtr->v0 > edgePtr->v1)
        sSwap(edgePtr->v0,edgePtr->v1);

      edgePtr++;
    }
  }

  // sort edge list (using heapsort)
  HeapSortEdges(edges,numEdges);

  // generate adjacency
  sInt last0 = -1, last1 = -1, count = 0, temp[2];
  sBool closed = sTRUE;

  for(sInt i=0;i<numEdges;i++)
  {
    edgePtr = &edges[i];

    if(last0 == edgePtr->v0 && last1 == edgePtr->v1)
    {
      temp[count++] = edgePtr->FaceVert;
      if(count == 2) // got a complete edge
      {
        ConnectFaces(Faces.Array,temp,count);
        count = 0;
      }
    }
    else
    {
      if(count != 0)
        closed = sFALSE;

      ConnectFaces(Faces.Array,temp,count);

      temp[0] = edgePtr->FaceVert;
      count = 1;
    }

    last0 = edgePtr->v0;
    last1 = edgePtr->v1;
  }

  if(count != 0)
    closed = sFALSE;

  ConnectFaces(Faces.Array,temp,count);

  // cleanup
  delete[] edges;

  return closed;
}

sBool GenMinMesh::CalcAdjacency()
{
  sInt *remap = CalcMergeVerts();
  sBool result = CalcAdjacencyCore(remap);
  delete[] remap;

  return result;
}

void GenMinMesh::VerifyAdjacency()
{
  sBool closed = sTRUE;

  for(sInt i=0;i<Faces.Count;i++)
  {
    GenMinFace *face = &Faces[i];
    if(face->Count < 2)
      continue;
    else if(face->Count == 2)
    {
      sInt v0 = face->Vertices[0];
      sInt v1 = face->Vertices[1];

      sVERIFY(!sCmpMem(&Vertices[v0].Pos,&Vertices[v1].Pos,sizeof(GenMinVector)));
    }
    else
    {
      for(sInt j=0;j<face->Count;j++)
      {
        sInt opposite = face->Adjacent[j];
        if(opposite != -1)
        {
          sVERIFY(Faces[opposite >> 3].Adjacent[opposite & 7] == i*8+j);
        }
        else
          closed = sFALSE;
      }
    }
  }

  if(!closed)
    sDPrintF("mesh is not closed\n");
}

/****************************************************************************/

#if sLINK_MINMESH
void GenMinMesh::Prepare()
{
  if(!PreparedMesh)
  {
    PreparedMesh = new EngMesh;
    PreparedMesh->FromGenMinMesh(this);
  }
}
#endif

void GenMinMesh::UnPrepare()
{
  if(PreparedMesh)
  {
    PreparedMesh->Release();
    PreparedMesh = 0;
  }
}

#if !sPLAYER

void GenMinMesh::PrepareWire(sInt flags,sU32 selMask)
{
  if(!WireMesh || WireFlags != flags || WireSelMask != selMask)
  {
    UnPrepareWire();

    WireMesh = new EngMesh;
    WireMesh->FromGenMinMeshWire(this,flags,selMask);
    WireFlags = flags;
    WireSelMask = selMask;
  }
}

void GenMinMesh::UnPrepareWire()
{
  if(WireMesh)
  {
    WireMesh->Release();
    WireMesh = 0;
  }
}

#endif

sBool GenMinMesh::Strip()
{
  if(PreparedMesh)
  {
    for(sInt i=0;i<Clusters.Count;i++)
      if(Clusters[i].Mtrl)
        Clusters[i].Mtrl->Release();

    Vertices.Resize(0);
    Faces.Resize(0);
    Clusters.Resize(0);

    Vertices.Compact();
    Faces.Compact();
    Clusters.Compact();

    Stripped = sTRUE;
    return sTRUE;
  }
  else
    return sFALSE;
}

sBool GenMinMesh::IsStripped()
{
  return Stripped;
}

void GenMinMesh::BakeAnimation(sF32 fade,sF32 metamorph)
{
  if(!Animation)
    return;

//  sDPrintF("%f\n",fade);

  sMatrix *matrices = new sMatrix[Animation->Matrices.Count];
  Animation->EvalAnimation(fade,metamorph,matrices);

  GenMinVert *vp = Vertices.Array;
  for(sInt i=0;i<Vertices.Count;i++,vp++)
  {
    sVector v0,v1,v;

    if(vp->BoneCount>0)
    {
      v1.Init(0,0,0,0);
      v0.Init(vp->Pos.x,vp->Pos.y,vp->Pos.z,1);

      for(sInt j=0;j<vp->BoneCount;j++)
      {
        v.Rotate34(matrices[vp->Matrix[j]],v0);
        v1.AddScale3(v,vp->Weights[j]);
      }

      vp->Pos.Init(v1);
      vp->BoneCount = 0;
    }
  }

  delete[] matrices;

  sRelease(Animation);
  for(sInt i=1;i<Clusters.Count;i++)
    Clusters[i].AnimType = 0;
  NormalsOK = 0;
}

void GenMinMesh::AutoStitch()
{
  sInt *merge = CalcMergeVerts();
  sInt *cycleNext = new sInt[Vertices.Count];
  sInt *cycleLast = new sInt[Vertices.Count];

  // build cycle links
  for(sInt i=0;i<Vertices.Count;i++)
  {
    sInt first = merge[i];

    if(first == i)
    {
      cycleNext[i] = i;
      cycleLast[i] = i;
    }
    else
    {
      cycleNext[i] = first;
      cycleNext[cycleLast[first]] = i;
      cycleLast[first] = i;
    }
  }

  // add stitches
  for(sInt i=0;i<Vertices.Count;i++)
  {
    sInt next = cycleNext[i];

    if(next != i)
    {
      GenMinFace *stitch = Faces.Add();

      stitch->Select = 0;
      stitch->Count = 2;
      stitch->Cluster = 0;
      stitch->Vertices[0] = i;
      stitch->Vertices[1] = next;
      stitch->Flags = 0;
    }
  }

  delete[] merge;
  delete[] cycleNext;
  delete[] cycleLast;
}

/****************************************************************************/
/****************************************************************************/
/***                                                                      ***/
/***   Ops                                                                ***/
/***                                                                      ***/
/****************************************************************************/
/****************************************************************************/

sBool CheckMinMesh(GenMinMesh *&mesh)
{
  GenMinMesh *oldmesh;
  if(mesh==0)
    return 1;
  if(mesh->ClassId!=KC_MINMESH)
    return 1;
  if(mesh->RefCount>1) 
  {
    oldmesh = mesh;
    mesh = new GenMinMesh;
    mesh->Copy(oldmesh);
    oldmesh->Release();
  }
  return 0;
}

/****************************************************************************/
/***                                                                      ***/
/***   Generator Ops                                                      ***/
/***                                                                      ***/
/****************************************************************************/

GenMinMesh * __stdcall MinMesh_SingleVert()
{
  GenMinMesh *mesh;
  GenMinVert *vp;
  GenMinFace *fp;

  mesh = new GenMinMesh;

  vp = mesh->Vertices.Add();
  sSetMem(vp,0,sizeof(*vp));
  vp->Color = 0;//~0;

  fp = mesh->Faces.Add();
  sSetMem(fp,0,sizeof(*fp));
  fp->Count = 1;
  fp->Cluster = 1;
  fp->Flags = 0;

  return mesh;
}

GenMinMesh * __stdcall MinMesh_Grid(sInt mode,sInt tx,sInt ty)
{
  GenMinMesh *mesh;
  sMatrix mat;

  mesh = new GenMinMesh;

  mesh->MakeGrid(tx,ty,0);
  if(mode&1)
    mesh->MakeGrid(tx,ty,16);
  mat.Init();
  mat.i.Init(-1,0,0,0);
  mat.j.Init(0,0,1,0);
  mesh->Transform(MMU_ALL,mat);
  mesh->SelectAll(MMU_ALL,MMS_SET);
  
  return mesh;
}

GenMinMesh * __stdcall MinMesh_Cube(sInt tx,sInt ty,sInt tz,sInt flags,sFSRT srt)
{
  GenMinMesh *mesh;
  sMatrix mat;
  sInt *tess=&tx;

  const static sS8 cube[6][9] =
  {
    { 0,1,  1, 1,  0, 0,-1 ,1 ,  0 },  
    { 2,1,  1, 1, -1, 0, 0 ,0 , 16 },  
    { 0,1,  1, 1,  0, 0, 1 ,3 , 16 },  
    { 2,1,  1, 1,  1, 0, 0 ,2 ,  0 },  
    { 0,2,  1, 1,  0, 1, 0 ,0 ,  0 },  
    { 0,2,  1, 1,  0,-1, 0 ,0 , 16 },  
  };
  const static sS8 sign[2] = { -1,1 };

  mat.Init();
  mesh = new GenMinMesh;
  for(sInt i=0;i<6;i++)
  {
    sSetMem(&mat,0,sizeof(mat));
    mesh->MakeGrid(tess[cube[i][0]],tess[cube[i][1]],cube[i][8]);

    (&mat.i.x)[cube[i][0]] = cube[i][2];
    (&mat.j.x)[cube[i][1]] = cube[i][3];
    mat.l.Init(cube[i][4]*0.5f,cube[i][5]*0.5f,cube[i][6]*0.5f,1);
    mesh->Transform(MMU_SELECTED,mat);

    mat.Init();
    if(cube[i][8])
    {
      mat.l.x = 1;
      mat.i.x = -1;
    }
    if(flags&2)            // wraparound
    {
      mat.l.x += cube[i][7];
    }
    mesh->Transform(MMU_SELECTED,mat,1,1);

    mesh->SelectAll(MMU_ALL,MMS_SETNOT);
  }
  mesh->SelectAll(MMU_ALL,MMS_SET);

  // post transform
  mat.InitSRT(srt.v);
  mesh->Transform(MMU_ALL,mat);

  // scale uv)

  if(flags&8)
  {
    mat.Init();
    mat.i.x = srt.s.x;
    mat.j.y = srt.s.y;
    mat.k.z = srt.s.z;
    mesh->Transform(MMU_ALL,mat,1,1);
  }


  // center / bottom
  if(flags&4)
  {
    mat.Init();
    mat.l.y = srt.s.y/2;
    mesh->Transform(MMU_ALL,mat);
  }

  return mesh;
}

GenMinMesh * __stdcall MinMesh_Torus(sInt tx,sInt ty,sF32 ro,sF32 ri,sF32 phase,sF32 arclen,sInt flags)
{
  GenMinMesh *mesh;
  GenMinVert *vp;
  sF32 fx,fy;
  sInt closed;

  closed = (arclen == 1.0f);
  mesh = new GenMinMesh;
  mesh->MakeGrid(ty,tx,closed?12:8+3);

  if(flags&1) // absolute radii
  {
    ri = (ro - ri) * 0.5f;
    ro -= ri;
  }

  vp = mesh->Vertices.Array;
  for(sInt i=0;i<mesh->Vertices.Count;i++)
  {
    fx = (1-vp->UV[1][0]-(phase/ty))*sPI2F;
    fy = ((vp->UV[closed][1])*arclen)*sPI2F;
    vp->Pos.x = -sFCos(fy)*(ro+sFSin(fx)*ri);
    vp->Pos.y = -sFCos(fx)*ri;
    vp->Pos.z = sFSin(fy)*(ro+sFSin(fx)*ri);
    vp++;
  }
  if(!closed)
  {
    vp[-2].Pos.x = -sFCos(arclen*sPI2F)*ro;
    vp[-2].Pos.y = 0;
    vp[-2].Pos.z = sFSin(arclen*sPI2F)* ro;
    vp[-1].Pos.x = -sFCos(0)*ro;
    vp[-1].Pos.y = 0;
    vp[-1].Pos.z = sFSin(0)*ro;
  }

  // center / bottom
  if(flags&2)
  {
    sMatrix mat;
    mat.Init();
    mat.l.y = ri;
    mesh->Transform(MMU_ALL,mat);
  }

  return mesh;
}

GenMinMesh * __stdcall MinMesh_Sphere(sInt tx,sInt ty)
{
  GenMinMesh *mesh;
  GenMinVert *vp;
  sF32 fx,fy;

  mesh = new GenMinMesh;
  mesh->MakeGrid(tx,ty,3+8);

  vp = mesh->Vertices.Array;
  for(sInt i=0;i<mesh->Vertices.Count-2;i++)
  {
    fx = (1-vp->UV[1][0])*sPI2F;
    fy = ((0.5f/(ty+1.0f))+(vp->UV[0][1]*((ty)/(ty+1.0f))))*sPIF;
    vp->Pos.x = -sFSin(fy)*sFSin(fx)*0.5f;
    vp->Pos.y = sFCos(fy)*0.5f;
    vp->Pos.z = -sFSin(fy)*sFCos(fx)*0.5f;
    vp++;
  }
  vp->Pos.y = mesh->Vertices[0].Pos.y;
  vp++;
  vp->Pos.y = vp[-2].Pos.y;
  vp++;

  return mesh;
}

GenMinMesh * __stdcall MinMesh_Cylinder(sInt tx,sInt ty,sInt flags,sInt tz,sInt arc)
{
  GenMinMesh *mesh;
  GenMinVert *vp;
  sF32 fx,fy;
  sInt x,y;
  sInt closed;
  sInt count;

  if(tx<3) tx=3;
  if(ty<1) ty=1;
  if(tz<1) tz=1;

  count = tx;
  if(arc>tx-1)
    arc = tx-1;
  if(arc>0)
    tx = count - arc + 2;

  closed = (flags & 1) ? 32 : 0;
  mesh = new GenMinMesh;

  // middle

  vp = mesh->MakeGrid(tx,ty,8);
  for(y=0;y<=ty;y++)
  {
    for(x=0;x<=tx;x++)
    {
      fx = ((x==tx)?0:x)*sPI2F/count;
      fy = y*1.0f/ty;
      if(x!=tx-1 || arc==0)
      {
        vp->Pos.x = sFSin(fx)*0.5f;
        vp->Pos.z = -sFCos(fx)*0.5f;
      }
      else
      {
        vp->Pos.x = 0;
        vp->Pos.z = 0;
      }
      vp->Pos.y = fy-0.5f;
      vp->UV[0][0] = x*1.0f/tx;
      vp->UV[0][1] = 1-fy;
      vp++;
    }
  }
  MinMesh_SelectAll(mesh,4);
//  mesh->SelectAll(MMU_ALL,MMS_);

  // bottom

  vp = mesh->MakeGrid(tx,tz-1,closed|1);
  for(y=0;y<tz;y++)
  {
    for(x=0;x<=tx;x++)
    {
      fx = ((x==tx)?0:x)*sPI2F/count;
      fy = (y+1)*0.5f/(tz);
      if(x!=tx-1 || arc==0)
      {
        vp->Pos.x = sFSin(fx)*0.5f;
        vp->Pos.z = -sFCos(fx)*0.5f;
      }
      else
      {
        vp->Pos.x = 0;
        vp->Pos.z = 0;
      }
      vp->Pos.y = -0.5f;
      vp->UV[0][0] = vp->Pos.x+0.5f;
      vp->UV[0][1] = vp->Pos.z+0.5f;
      vp++;
    }
  }
  vp->Pos.y = -0.5f;
  vp->UV[0][0] = 0.5f;
  vp->UV[0][1] = 0.5f;
  vp++;

  // top

  vp = mesh->MakeGrid(tx,tz-1,closed|2);
  for(y=tz-1;y>=0;y--)
  {
    for(x=0;x<=tx;x++)
    {
      fx = ((x==tx)?0:x)*sPI2F/count;
      fy = (y+1)*0.5f/(tz);
      if(x!=tx-1 || arc==0)
      {
        vp->Pos.x = sFSin(fx)*0.5f;
        vp->Pos.z = -sFCos(fx)*0.5f;
      }
      else
      {
        vp->Pos.x = 0;
        vp->Pos.z = 0;
      }
      vp->Pos.y = 0.5f;
      vp->UV[0][0] = vp->Pos.x+0.5f;
      vp->UV[0][1] = -vp->Pos.z+0.5f;
      vp++;
    }
  }
  vp->Pos.y = 0.5f;
  vp->UV[0][0] = 0.5f;
  vp->UV[0][1] = 0.5f;
  vp++;

  // center / bottom
  if(flags&2)
  {
    sMatrix mat; mat.Init();
    mat.l.y = 0.5f;
    mesh->Transform(MMU_ALL,mat);
  }

  return mesh;
}

GenMinMesh * __stdcall MinMesh_XSI(KOp *op,sChar *filename)
{
  GenMinMesh *mesh = 0;

#if sLINK_LOADER
  sLoader::Scene *scene = new sLoader::Scene;
  if(scene->LoadXSI(filename))
  {
    mesh = new GenMinMesh;
    scene->CreateMesh(mesh);
    mesh->AutoStitch();

    sInt size = 0;
    sU8 *exportedMesh = mesh->ExportToBlob(size);
    op->SetBlob(exportedMesh,size);

    sDPrintF("mesh->blob export ok, size: %d bytes\n",size);

    delete mesh;
    mesh = 0;
  }
  else
    op->SetBlob(0,0);

  delete scene;
#endif

  mesh = new GenMinMesh;
  if(op->GetBlobSize())
    mesh->ImportFromBlob(op->GetBlobData());
  else
    mesh->MakeGrid(1,1,0);

  return mesh;
}

/****************************************************************************/

GenMinMesh * __stdcall MinMesh_OBJ(KOp *op,sChar *filename)
{
  GenMinMesh *mesh = 0;

#if sLINK_LOADER
  OBJFileReader reader;
  if(reader.read(filename))
  {
    mesh = new GenMinMesh;
    mesh->Vertices.AtLeast(reader.vertices.size());
    for(sInt i=0;i<(sInt)reader.vertices.size();i++)
    {
      const OBJFileReader::VertInfo& inVert = reader.vertices[i];
      GenMinVert* outVert = mesh->Vertices.Add();

      sSetMem(outVert,0,sizeof(GenMinVert));
      outVert->Pos.Init(reader.positions[inVert.pos]);
      if(inVert.norm != -1)
        outVert->Normal.Init(reader.normals[inVert.norm]);
      if(inVert.tex != -1)
      {
        outVert->UV[0][0] = reader.texcoords[inVert.tex].x;
        outVert->UV[0][1] = reader.texcoords[inVert.tex].y;
      }
    }

    mesh->Faces.AtLeast(reader.faces.size());

    for(sInt i=0;i<(sInt)reader.faces.size();i++)
    {
      const OBJFileReader::FaceInfo& inFace = reader.faces[i];
      GenMinFace* outFace = mesh->Faces.Add();

      sSetMem(outFace,0,sizeof(GenMinFace));
      outFace->Cluster = 1;
      outFace->Count = inFace.count;
      for(sInt j=0;j<inFace.count;j++)
        outFace->Vertices[j] = inFace.start + j;
    }

    mesh->AutoStitch();
    mesh->CalcNormals();
  }
#endif

  return mesh;
}

/****************************************************************************/

static sS16 KopuliData[][12] =
{
  { 3066,6360,-2241,4152,9760,-2241,895,11405,-2241,895,11405,-2241,  },
  { 3066,6360,-2241,895,11405,-2241,-388,8663,-2241,-388,8663,-2241,  },
  { 3066,6360,-2241,-388,8663,-2241,1586,5044,-2241,1586,5044,-2241,  },
  { 3066,6360,-2241,8988,-1,-2241,8001,3399,-2241,8001,3399,-2241,  },
  { 3066,6360,-2241,1586,5044,-2241,8988,-1,-2241,8988,-1,-2241,  },
  { 4152,9760,-2241,6422,10637,-2241,3461,13269,-2241,3461,13269,-2241,  },
  { 4152,9760,-2241,6817,7566,-2241,6422,10637,-2241,6422,10637,-2241,  },
  { 4152,9760,-2241,1191,14037,-2241,895,11405,-2241,895,11405,-2241,  },
  { 4152,9760,-2241,3461,13269,-2241,1191,14037,-2241,1191,14037,-2241,  },
  { 6817,7566,-2241,8594,11953,-2241,6422,10637,-2241,6422,10637,-2241,  },
  { 8594,11953,-2241,9877,11076,-2241,8001,14037,-2241,8001,14037,-2241,  },
  { 8594,11953,-2241,8001,14037,-2241,6422,10637,-2241,6422,10637,-2241,  },
  { 9877,11076,-2241,10666,12501,-2241,8001,14037,-2241,8001,14037,-2241,  },
  { 3461,13269,-2241,5337,15024,-2241,303,15901,-2241,303,15901,-2241,  },
  { 3461,13269,-2241,303,15901,-2241,1191,14037,-2241,1191,14037,-2241,  },
  { 5337,15024,-2241,5731,18423,-2241,303,15901,-2241,303,15901,-2241,  },
  { 5731,18423,-2241,3363,20507,-2241,303,15901,-2241,303,15901,-2241,  },
  { 3363,20507,-2241,402,18752,-2241,303,15901,-2241,303,15901,-2241,  },
  { 1191,14037,-2241,-881,14585,-2241,895,11405,-2241,895,11405,-2241,  },
  { -881,14585,-2241,-5224,16120,-2241,895,11405,-2241,895,11405,-2241,  },
  { -881,14585,-2241,-5224,18423,-2241,-5224,16120,-2241,-5224,16120,-2241,  },
  { -5224,18423,-2241,-8481,16340,-2241,-5224,16120,-2241,-5224,16120,-2241,  },
  { -8481,16340,-2241,-7198,14256,-2241,-5224,16120,-2241,-5224,16120,-2241,  },
  { -388,8663,-2241,204,5483,-2241,1586,5044,-2241,1586,5044,-2241,  },
  { -388,8663,-2241,-1967,6908,-2241,204,5483,-2241,204,5483,-2241,  },
  { -388,8663,-2241,-2461,10747,-2241,-1967,6908,-2241,-1967,6908,-2241,  },
  { -2461,10747,-2241,-4435,7018,-2241,-1967,6908,-2241,-1967,6908,-2241,  },
  { -4435,7018,-2241,-6606,6250,-2241,-4237,3838,-2241,-4237,3838,-2241,  },
  { -4435,7018,-2241,-4237,3838,-2241,-1967,6908,-2241,-1967,6908,-2241,  },
  { -4435,7018,-2241,-6409,9869,-2241,-6606,6250,-2241,-6606,6250,-2241,  },
  { -6409,9869,-2241,-9271,6360,-2241,-6606,6250,-2241,-6606,6250,-2241,  },
  { -9271,6360,-2241,-12331,7084,-2241,-9172,2631,-2241,-9172,2631,-2241,  },
  { -9271,6360,-2241,-9172,2631,-2241,-6606,6250,-2241,-6606,6250,-2241,  },
  { -9271,6360,-2241,-10850,9387,-2241,-12331,7084,-2241,-12331,7084,-2241,  },
  { 204,5483,-2241,-388,2302,-2241,1586,5044,-2241,1586,5044,-2241,  },
  { -388,2302,-2241,895,1206,-2241,1586,5044,-2241,1586,5044,-2241,  },
  { 8988,-1,-2241,12245,3728,-2241,8001,3399,-2241,8001,3399,-2241,  },
  { 12245,3728,-2241,10370,6579,-2241,8001,3399,-2241,8001,3399,-2241,  },
  { 4152,9760,2200,4152,9760,-2241,3066,6360,-2241,3066,6360,-2241,  },
  { 3066,6360,-2241,3066,6360,2200,4152,9760,2200,4152,9760,2200,  },
  { 3066,6360,-2241,8001,3399,-2241,8001,3399,2200,8001,3399,2200,  },
  { 8001,3399,2200,3066,6360,2200,3066,6360,-2241,3066,6360,-2241,  },
  { 6817,7566,2200,6817,7566,-2241,4152,9760,-2241,4152,9760,-2241,  },
  { 4152,9760,-2241,4152,9760,2200,6817,7566,2200,6817,7566,2200,  },
  { 8594,11953,2200,8594,11953,-2241,6817,7566,-2241,6817,7566,-2241,  },
  { 6817,7566,-2241,6817,7566,2200,8594,11953,2200,8594,11953,2200,  },
  { 9877,11076,2200,9877,11076,-2241,8594,11953,-2241,8594,11953,-2241,  },
  { 8594,11953,-2241,8594,11953,2200,9877,11076,2200,9877,11076,2200,  },
  { 10666,12501,2200,10666,12501,-2241,9877,11076,-2241,9877,11076,-2241,  },
  { 9877,11076,-2241,9877,11076,2200,10666,12501,2200,10666,12501,2200,  },
  { 8001,14037,2200,8001,14037,-2241,10666,12501,-2241,10666,12501,-2241,  },
  { 10666,12501,-2241,10666,12501,2200,8001,14037,2200,8001,14037,2200,  },
  { 6422,10637,2200,6422,10637,-2241,8001,14037,-2241,8001,14037,-2241,  },
  { 8001,14037,-2241,8001,14037,2200,6422,10637,2200,6422,10637,2200,  },
  { 3461,13269,2200,3461,13269,-2241,6422,10637,-2241,6422,10637,-2241,  },
  { 6422,10637,-2241,6422,10637,2200,3461,13269,2200,3461,13269,2200,  },
  { 5337,15024,2200,5337,15024,-2241,3461,13269,-2241,3461,13269,-2241,  },
  { 3461,13269,-2241,3461,13269,2200,5337,15024,2200,5337,15024,2200,  },
  { 5731,18423,2200,5731,18423,-2241,5337,15024,-2241,5337,15024,-2241,  },
  { 5337,15024,-2241,5337,15024,2200,5731,18423,2200,5731,18423,2200,  },
  { 3363,20507,2200,3363,20507,-2241,5731,18423,-2241,5731,18423,-2241,  },
  { 5731,18423,-2241,5731,18423,2200,3363,20507,2200,3363,20507,2200,  },
  { 402,18752,2200,402,18752,-2241,3363,20507,-2241,3363,20507,-2241,  },
  { 3363,20507,-2241,3363,20507,2200,402,18752,2200,402,18752,2200,  },
  { 303,15901,2200,303,15901,-2241,402,18752,-2241,402,18752,-2241,  },
  { 402,18752,-2241,402,18752,2200,303,15901,2200,303,15901,2200,  },
  { 1191,14037,2200,1191,14037,-2241,303,15901,-2241,303,15901,-2241,  },
  { 303,15901,-2241,303,15901,2200,1191,14037,2200,1191,14037,2200,  },
  { -881,14585,2200,-881,14585,-2241,1191,14037,-2241,1191,14037,-2241,  },
  { 1191,14037,-2241,1191,14037,2200,-881,14585,2200,-881,14585,2200,  },
  { -5224,18423,2200,-5224,18423,-2241,-881,14585,-2241,-881,14585,-2241,  },
  { -881,14585,-2241,-881,14585,2200,-5224,18423,2200,-5224,18423,2200,  },
  { -8481,16340,2200,-8481,16340,-2241,-5224,18423,-2241,-5224,18423,-2241,  },
  { -5224,18423,-2241,-5224,18423,2200,-8481,16340,2200,-8481,16340,2200,  },
  { -7198,14256,2200,-7198,14256,-2241,-8481,16340,-2241,-8481,16340,-2241,  },
  { -8481,16340,-2241,-8481,16340,2200,-7198,14256,2200,-7198,14256,2200,  },
  { -5224,16120,2200,-5224,16120,-2241,-7198,14256,-2241,-7198,14256,-2241,  },
  { -7198,14256,-2241,-7198,14256,2200,-5224,16120,2200,-5224,16120,2200,  },
  { 895,11405,2200,895,11405,-2241,-5224,16120,-2241,-5224,16120,-2241,  },
  { -5224,16120,-2241,-5224,16120,2200,895,11405,2200,895,11405,2200,  },
  { -388,8663,2200,-388,8663,-2241,895,11405,-2241,895,11405,-2241,  },
  { 895,11405,-2241,895,11405,2200,-388,8663,2200,-388,8663,2200,  },
  { -2461,10747,2200,-2461,10747,-2241,-388,8663,-2241,-388,8663,-2241,  },
  { -388,8663,-2241,-388,8663,2200,-2461,10747,2200,-2461,10747,2200,  },
  { -4435,7018,2200,-4435,7018,-2241,-2461,10747,-2241,-2461,10747,-2241,  },
  { -2461,10747,-2241,-2461,10747,2200,-4435,7018,2200,-4435,7018,2200,  },
  { -6409,9869,2200,-6409,9869,-2241,-4435,7018,-2241,-4435,7018,-2241,  },
  { -4435,7018,-2241,-4435,7018,2200,-6409,9869,2200,-6409,9869,2200,  },
  { -9271,6360,2200,-9271,6360,-2241,-6409,9869,-2241,-6409,9869,-2241,  },
  { -6409,9869,-2241,-6409,9869,2200,-9271,6360,2200,-9271,6360,2200,  },
  { -10850,9387,2200,-10850,9387,-2241,-9271,6360,-2241,-9271,6360,-2241,  },
  { -9271,6360,-2241,-9271,6360,2200,-10850,9387,2200,-10850,9387,2200,  },
  { -12331,7084,2200,-12331,7084,-2241,-10850,9387,-2241,-10850,9387,-2241,  },
  { -10850,9387,-2241,-10850,9387,2200,-12331,7084,2200,-12331,7084,2200,  },
  { -9172,2631,2200,-9172,2631,-2241,-12331,7084,-2241,-12331,7084,-2241,  },
  { -12331,7084,-2241,-12331,7084,2200,-9172,2631,2200,-9172,2631,2200,  },
  { -6606,6250,2200,-6606,6250,-2241,-9172,2631,-2241,-9172,2631,-2241,  },
  { -9172,2631,-2241,-9172,2631,2200,-6606,6250,2200,-6606,6250,2200,  },
  { -4237,3838,2200,-4237,3838,-2241,-6606,6250,-2241,-6606,6250,-2241,  },
  { -6606,6250,-2241,-6606,6250,2200,-4237,3838,2200,-4237,3838,2200,  },
  { -1967,6908,2200,-1967,6908,-2241,-4237,3838,-2241,-4237,3838,-2241,  },
  { -4237,3838,-2241,-4237,3838,2200,-1967,6908,2200,-1967,6908,2200,  },
  { 204,5483,2200,204,5483,-2241,-1967,6908,-2241,-1967,6908,-2241,  },
  { -1967,6908,-2241,-1967,6908,2200,204,5483,2200,204,5483,2200,  },
  { -388,2302,2200,-388,2302,-2241,204,5483,-2241,204,5483,-2241,  },
  { 204,5483,-2241,204,5483,2200,-388,2302,2200,-388,2302,2200,  },
  { 895,1206,2200,895,1206,-2241,-388,2302,-2241,-388,2302,-2241,  },
  { -388,2302,-2241,-388,2302,2200,895,1206,2200,895,1206,2200,  },
  { 1586,5044,2200,1586,5044,-2241,895,1206,-2241,895,1206,-2241,  },
  { 895,1206,-2241,895,1206,2200,1586,5044,2200,1586,5044,2200,  },
  { 8988,0,2200,8988,-1,-2241,1586,5044,-2241,1586,5044,-2241,  },
  { 1586,5044,-2241,1586,5044,2200,8988,0,2200,8988,0,2200,  },
  { 12245,3728,2200,12245,3728,-2241,8988,-1,-2241,8988,-1,-2241,  },
  { 8988,-1,-2241,8988,0,2200,12245,3728,2200,12245,3728,2200,  },
  { 10370,6579,2200,10370,6579,-2241,12245,3728,-2241,12245,3728,-2241,  },
  { 12245,3728,-2241,12245,3728,2200,10370,6579,2200,10370,6579,2200,  },
  { 8001,3399,2200,8001,3399,-2241,10370,6579,-2241,10370,6579,-2241,  },
  { 10370,6579,-2241,10370,6579,2200,8001,3399,2200,8001,3399,2200,  },
  { 895,11405,2200,4152,9760,2200,3066,6360,2200,3066,6360,2200,  },
  { -388,8663,2200,895,11405,2200,3066,6360,2200,3066,6360,2200,  },
  { 1586,5044,2200,-388,8663,2200,3066,6360,2200,3066,6360,2200,  },
  { 8001,3399,2200,8988,0,2200,3066,6360,2200,3066,6360,2200,  },
  { 8988,0,2200,1586,5044,2200,3066,6360,2200,3066,6360,2200,  },
  { 3461,13269,2200,6422,10637,2200,4152,9760,2200,4152,9760,2200,  },
  { 6422,10637,2200,6817,7566,2200,4152,9760,2200,4152,9760,2200,  },
  { 895,11405,2200,1191,14037,2200,4152,9760,2200,4152,9760,2200,  },
  { 1191,14037,2200,3461,13269,2200,4152,9760,2200,4152,9760,2200,  },
  { 6422,10637,2200,8594,11953,2200,6817,7566,2200,6817,7566,2200,  },
  { 8001,14037,2200,9877,11076,2200,8594,11953,2200,8594,11953,2200,  },
  { 6422,10637,2200,8001,14037,2200,8594,11953,2200,8594,11953,2200,  },
  { 8001,14037,2200,10666,12501,2200,9877,11076,2200,9877,11076,2200,  },
  { 303,15901,2200,5337,15024,2200,3461,13269,2200,3461,13269,2200,  },
  { 1191,14037,2200,303,15901,2200,3461,13269,2200,3461,13269,2200,  },
  { 303,15901,2200,5731,18423,2200,5337,15024,2200,5337,15024,2200,  },
  { 303,15901,2200,3363,20507,2200,5731,18423,2200,5731,18423,2200,  },
  { 303,15901,2200,402,18752,2200,3363,20507,2200,3363,20507,2200,  },
  { 895,11405,2200,-881,14585,2200,1191,14037,2200,1191,14037,2200,  },
  { 895,11405,2200,-5224,16120,2200,-881,14585,2200,-881,14585,2200,  },
  { -5224,16120,2200,-5224,18423,2200,-881,14585,2200,-881,14585,2200,  },
  { -5224,16120,2200,-8481,16340,2200,-5224,18423,2200,-5224,18423,2200,  },
  { -5224,16120,2200,-7198,14256,2200,-8481,16340,2200,-8481,16340,2200,  },
  { 1586,5044,2200,204,5483,2200,-388,8663,2200,-388,8663,2200,  },
  { 204,5483,2200,-1967,6908,2200,-388,8663,2200,-388,8663,2200,  },
  { -1967,6908,2200,-2461,10747,2200,-388,8663,2200,-388,8663,2200,  },
  { -1967,6908,2200,-4435,7018,2200,-2461,10747,2200,-2461,10747,2200,  },
  { -4237,3838,2200,-6606,6250,2200,-4435,7018,2200,-4435,7018,2200,  },
  { -1967,6908,2200,-4237,3838,2200,-4435,7018,2200,-4435,7018,2200,  },
  { -6606,6250,2200,-6409,9869,2200,-4435,7018,2200,-4435,7018,2200,  },
  { -6606,6250,2200,-9271,6360,2200,-6409,9869,2200,-6409,9869,2200,  },
  { -9172,2631,2200,-12331,7084,2200,-9271,6360,2200,-9271,6360,2200,  },
  { -6606,6250,2200,-9172,2631,2200,-9271,6360,2200,-9271,6360,2200,  },
  { -12331,7084,2200,-10850,9387,2200,-9271,6360,2200,-9271,6360,2200,  },
  { 1586,5044,2200,-388,2302,2200,204,5483,2200,204,5483,2200,  },
  { 1586,5044,2200,895,1206,2200,-388,2302,2200,-388,2302,2200,  },
  { 8001,3399,2200,12245,3728,2200,8988,0,2200,8988,0,2200,  },
  { 8001,3399,2200,10370,6579,2200,12245,3728,2200,12245,3728,2200,  },
};

GenMinMesh * __stdcall MinMesh_Kopuli()
{
  GenMinMesh *mesh;
  sInt i,fc;
  GenMinVert *v;
  GenMinFace *f;

  fc = sizeof(KopuliData)/24;

  mesh = new GenMinMesh;

  mesh->Vertices.SetMax(fc*4);
  mesh->Vertices.Count = fc*4;
  v = mesh->Vertices.Array;
  sSetMem(v,0,sizeof(*v)*(fc*4));

  mesh->Faces.SetMax(fc);
  mesh->Faces.Count = fc;
  f = mesh->Faces.Array;
  sSetMem(f,0,sizeof(*f)*fc);

  for(i=0;i<fc;i++)
  {
    v->Pos.x = KopuliData[i][ 0]/16384.0f;
    v->Pos.y = KopuliData[i][ 1]/16384.0f;
    v->Pos.z = KopuliData[i][ 2]/16384.0f;
    v->Select = 1;
    v->Color = 0;//~0;
    v++;
    v->Pos.x = KopuliData[i][ 3]/16384.0f;
    v->Pos.y = KopuliData[i][ 4]/16384.0f;
    v->Pos.z = KopuliData[i][ 5]/16384.0f;
    v->Select = 1;
    v->Color = 0;//~0;
    v++;
    v->Pos.x = KopuliData[i][ 6]/16384.0f;
    v->Pos.y = KopuliData[i][ 7]/16384.0f;
    v->Pos.z = KopuliData[i][ 8]/16384.0f;
    v->Select = 1;
    v->Color = 0;//~0;
    v++;
    v->Pos.x = KopuliData[i][ 9]/16384.0f;
    v->Pos.y = KopuliData[i][10]/16384.0f;
    v->Pos.z = KopuliData[i][11]/16384.0f;
    v->Select = 1;
    v->Color = 0;//~0;
    v++;

    f->Select = 1;
    f->Cluster = 1;
    f->Flags = 0;
    if(KopuliData[i][6]==KopuliData[i][9] && KopuliData[i][7]==KopuliData[i][10] && KopuliData[i][8]==KopuliData[i][11])
    {
      f->Count = 3;
      f->Vertices[0] = i*4+2;
      f->Vertices[1] = i*4+1;
      f->Vertices[2] = i*4+0;
    }
    else
    {
      f->Count = 4;
      f->Vertices[0] = i*4+3;
      f->Vertices[1] = i*4+2;
      f->Vertices[2] = i*4+1;
      f->Vertices[3] = i*4+0;
    }
    f++;
  }

  return mesh;
} 

/****************************************************************************/
/***                                                                      ***/
/***   Special Ops                                                        ***/
/***                                                                      ***/
/****************************************************************************/

GenMinMesh * __stdcall MinMesh_MatLink(GenMinMesh *mesh,GenMaterial *mtrl,sInt select,sInt pass)
{
  GenMinFace *face;

  if(!mtrl || mtrl->ClassId != KC_MATERIAL || CheckMinMesh(mesh))
    return 0;

  sInt id = mesh->AddCluster(mtrl,pass);
  for(sInt i=0;i<mesh->Faces.Count;i++)
  {
    face = &mesh->Faces[i];
    if(select==0 || (select==1&&face->Select) || (select==2&&!face->Select))
      if(face->Cluster!=0)
        face->Cluster = id;
  }
  mesh->ChangeTopo();
  mtrl->Release();

  return mesh;
}

/****************************************************************************/

GenMinMesh * __stdcall MinMesh_MatLinkId(GenMinMesh *mesh,GenMaterial *mtrl,sInt id,sInt pass)
{
  if(!mtrl || mtrl->ClassId != KC_MATERIAL || CheckMinMesh(mesh))
    return 0;

  for(sInt i=0;i<mesh->Clusters.Count;i++)
  {
    if(mesh->Clusters[i].Id==id)
    {
      sRelease(mesh->Clusters[i].Mtrl);
      mesh->Clusters[i].Mtrl = mtrl; mtrl->AddRef();
      mesh->Clusters[i].RenderPass = pass;
    }
  }

  mesh->ChangeTopo();
  mtrl->Release();

  return mesh;
}

/****************************************************************************/

GenMinMesh * __stdcall MinMesh_Add(sInt count,GenMinMesh *mesh,...)
{
  if(CheckMinMesh(mesh)) return 0;
  for(sInt i=1;i<count;i++)
  {
    GenMinMesh *b = (&mesh)[i];
    mesh->Add(b);
    b->Release();
  }
  mesh->ChangeTopo();
  mesh->MergeClusters();
  return mesh;
}

/****************************************************************************/

GenMinMesh * __stdcall MinMesh_SelectAll(GenMinMesh *mesh,sU32 mode)
{
  if(CheckMinMesh(mesh)) return 0;

  if(mode&2)
    for(sInt i=0;i<mesh->Vertices.Count;i++)
      mesh->Vertices[i].Select = (mode&1);
  if(mode&4)
    for(sInt i=0;i<mesh->Faces.Count;i++)
      mesh->Faces[i].Select = (mode&1);

  mesh->ChangeGeo();
  return mesh;
}

/****************************************************************************/

sBool SelectLogic(sInt old,sInt sel,sInt mode)
{
  switch(mode&3)
  {
  case MMS_ADD:
    if(sel) return 1;
    break;
  case MMS_SUB:
    if(sel) return 0;
    break;
  case MMS_SET:
    return sel;
    break;
  case MMS_SETNOT:
    return !sel;
    break;
  }
  return old;
}

GenMinMesh * __stdcall MinMesh_SelectCube(GenMinMesh *mesh,sInt mode,sF323 center,sF323 size)
{
  if(CheckMinMesh(mesh)) return 0;

  for(sInt i=0;i<mesh->Vertices.Count;i++)
  {
    sBool a = sFAbs(mesh->Vertices[i].Pos.x-center.x)<=size.x/2;
    sBool b = sFAbs(mesh->Vertices[i].Pos.y-center.y)<=size.y/2; 
    sBool c = sFAbs(mesh->Vertices[i].Pos.z-center.z)<=size.z/2;
    mesh->Vertices[i].TempByte = (a && b && c);
  }
  switch(mode&12)
  {
  case MMS_VERTEX:
    for(sInt i=0;i<mesh->Vertices.Count;i++)
      mesh->Vertices[i].Select = SelectLogic(mesh->Vertices[i].Select,mesh->Vertices[i].TempByte,mode);
    break;
  case MMS_FULLFACE:
    for(sInt i=0;i<mesh->Faces.Count;i++)
    {
      GenMinFace *face = &mesh->Faces[i];
      sBool ok = 1;
      for(sInt j=0;j<face->Count;j++)
        if(!mesh->Vertices[face->Vertices[j]].TempByte)
          ok = 0;
      face->Select = SelectLogic(face->Select,ok,mode);
    }
    break;
  case MMS_PARTFACE:
    for(sInt i=0;i<mesh->Faces.Count;i++)
    {
      GenMinFace *face = &mesh->Faces[i];
      sBool ok = 0;
      for(sInt j=0;j<face->Count;j++)
        if(mesh->Vertices[face->Vertices[j]].TempByte)
          ok = 1;
      face->Select = SelectLogic(face->Select,ok,mode);
    }
    break;
  }

  mesh->ChangeGeo();
  return mesh;
}


GenMinMesh * __stdcall MinMesh_SelectLogic(GenMinMesh *mesh,sInt mode)
{
  if(CheckMinMesh(mesh)) return 0;

  switch(mode)
  {
  case 0:
    for(sInt i=0;i<mesh->Faces.Count;i++)
      mesh->Faces[i].Select = !mesh->Faces[i].Select;
    break;

  case 1:
    for(sInt i=0;i<mesh->Vertices.Count;i++)
      mesh->Vertices[i].Select = !mesh->Vertices[i].Select;
    break;

  case 2:
    for(sInt i=0;i<mesh->Vertices.Count;i++)
      mesh->Vertices[i].Select = 0;
    for(sInt i=0;i<mesh->Faces.Count;i++)
    {
      GenMinFace *face = &mesh->Faces[i];
      if(face->Select)
        for(sInt j=0;j<face->Count;j++)
          mesh->Vertices[face->Vertices[j]].Select = 1;
    }
    break;
  }
  return mesh;
}

/****************************************************************************/

GenMinMesh * __stdcall MinMesh_DeleteFaces(GenMinMesh *mesh)
{
  if(CheckMinMesh(mesh)) return 0;

  for(sInt i=0;i<mesh->Faces.Count;i++)
    if(mesh->Faces[i].Select)
      mesh->Faces[i].Cluster = 0;

  mesh->ChangeTopo();
  return mesh;
}

/****************************************************************************/

GenMinMesh * __stdcall MinMesh_Invert(GenMinMesh *mesh)
{
  if(CheckMinMesh(mesh)) return 0;

  mesh->Invert();

  return mesh;
}

/****************************************************************************/
/***                                                                      ***/
/***   Geometry Modifiers                                                 ***/
/***                                                                      ***/
/****************************************************************************/

GenMinMesh * __stdcall MinMesh_TransformEx(GenMinMesh *mesh,sInt mask,sFSRT srt)
{
  sMatrix mat;

  if(CheckMinMesh(mesh)) return 0;

  mat.InitSRT(srt.v);
  mesh->Transform(mask&3,mat,(mask>>2)&7,(mask>>5)&7,(mask>>8)&3);  
  mesh->ChangeGeo();
  return mesh;
}

GenMinMesh * __stdcall MinMesh_Displace(GenMinMesh *mesh,class GenBitmap *bmp,sInt mask,sF323 ampli)
{
  BilinearContext ctx;
  sU16 height[4];

  if(CheckMinMesh(mesh)) return 0;
  mesh->CalcNormals();

  // prepare bilinear samples
  BilinearSetup(&ctx,bmp->Data,bmp->XSize,bmp->YSize,0);
  sF32 xScale = bmp->XSize * 65536.0f;
  sF32 yScale = bmp->YSize * 65536.0f;

  for(sInt i=0;i<mesh->Vertices.Count;i++)
  {
    GenMinVert *vert = &mesh->Vertices[i];
		if(mask==0 || (mask==1&&vert->Select) || (mask==2&&!vert->Select))
    {
      BilinearFilter(&ctx,(sU64 *)height,vert->UV[0][0]*xScale,vert->UV[0][1]*yScale);
      sF32 h = (height[0]-16384.0f) * (1.0f/32767.0f);
      vert->Pos.x += vert->Normal.x*h*ampli.x;
      vert->Pos.y += vert->Normal.y*h*ampli.y;
      vert->Pos.z += vert->Normal.z*h*ampli.z;
    }
  }
  mesh->ChangeGeo();
  bmp->Release();

  return mesh;
}

GenMinMesh * __stdcall MinMesh_Perlin(GenMinMesh *mesh,sInt mask,sFSRT srt,sF323 ampl)
{
  if(CheckMinMesh(mesh)) return 0;

  sMatrix mat; mat.InitSRT(srt.v);
  sVector amp; amp.Init(ampl.x,ampl.y,ampl.z,0);

  sInt oldcw;

  // setup fpu: single precision, round towards neg. infinity

  __asm
  {
    fstcw   [oldcw];
    push    0143fh;
    fldcw   [esp];
    pop     eax;
  }

	for(sInt i=0;i<mesh->Vertices.Count;i++)
	{
    GenMinVert *vert = &mesh->Vertices[i];
		if(mask==0 || (mask==1&&vert->Select) || (mask==2&&!vert->Select))
    {
      sVector t0,t1,t2;
      sVector pos;
      sF32 fs[3];
      sInt is[3];

      // rotate input vector, calc sampling points
      vert->Pos.Out(pos,1);
      t0.Rotate34(mat,pos);
      for(sInt j=0;j<3;j++)
      {
        is[j] = sFtol(t0[j]);   // integer coordinate
        fs[j] = t0[j] - is[j];  // fractional part
        is[j] &= 255;           // integer grid wraps round 256
        fs[j] = fs[j]*fs[j]*fs[j]*(10.0f+fs[j]*(6.0f*fs[j]-15.0f));
      }

#define ix is[0]
#define iy is[1]
#define iz is[2]
#define P sPerlinPermute
#define G sPerlinGradient3D

		  // trilinear interpolation of grid points
		  t0.Lin3(G[P[P[P[ix]+iy]+iz]&15],G[P[P[P[ix+1]+iy]+iz]&15],fs[0]);
		  t1.Lin3(G[P[P[P[ix]+iy+1]+iz]&15],G[P[P[P[ix+1]+iy+1]+iz]&15],fs[0]);
		  t0.Lin3(t0,t1,fs[1]);
		  t1.Lin3(G[P[P[P[ix]+iy]+iz+1]&15],G[P[P[P[ix+1]+iy]+iz+1]&15],fs[0]);
		  t2.Lin3(G[P[P[P[ix]+iy+1]+iz+1]&15],G[P[P[P[ix+1]+iy+1]+iz+1]&15],fs[0]);
		  t1.Lin3(t1,t2,fs[1]);
		  t0.Lin3(t0,t1,fs[2]);

#undef ix
#undef iy
#undef iz
#undef P
#undef G

      vert->Pos.x += t0.x*amp.x;
      vert->Pos.y += t0.y*amp.y;
      vert->Pos.z += t0.z*amp.z;
    }
	}

  // restore fpu state
  __asm
  {
    fldcw   [oldcw];
  }

  mesh->ChangeGeo();
  return mesh;
}

GenMinMesh * __stdcall MinMesh_ExtrudeNormal(GenMinMesh *mesh,sInt mask,sF32 distance)
{
  if(CheckMinMesh(mesh)) return 0;

  mesh->CalcNormals();
	for(sInt i=0;i<mesh->Vertices.Count;i++)
	{
    GenMinVert *vert = &mesh->Vertices[i];
		if(mask==0 || (mask==1&&vert->Select) || (mask==2&&!vert->Select))
      vert->Pos.AddScale(vert->Normal,distance);
  }

  mesh->ChangeGeo();
  return mesh;
}

GenMinMesh * __stdcall MinMesh_Bend2(GenMinMesh *mesh,sF323 center,sF323 rotate,sF32 len,sF32 angle)
{
  sMatrix mt,mb;
  sVector vt;
  sF32 vx,vy,t,sa,ca;
  sInt i;

  if(CheckMinMesh(mesh)) return 0;

  mt.InitEulerPI2(&rotate.x);
  mt.l.x = -center.x;
  mt.l.y = -center.y;
  mt.l.z = -center.z;
  mb = mt;
  mb.TransR();
  angle *= sPI2F;

  for(i=0;i<mesh->Vertices.Count;i++)
  {
    GenMinVert *vert = &mesh->Vertices[i];

    sVector pos;vert->Pos.Out(pos,1);
    vt.Rotate34(mt,pos);
    t = vt.y;
    if(t>=0.0f)
      vt.y -= sMin(t,len);

    t = sRange(t/len,1.0f,0.0f) * angle;

    sa = sFSin(t);
    ca = sFCos(t);
    vx = vt.x;
    vy = vt.y;
    vt.x = ca * vx - sa * vy;
    vt.y = sa * vx + ca * vy;

    vt.Rotate34(mb);
    vert->Pos.Init(vt);
  }

  mesh->ChangeGeo();
  return mesh;
}

GenMinMesh * __stdcall MinMesh_BakeAnimation(GenMinMesh *mesh,sF32 fade,sF32 metamorph)
{
  if(CheckMinMesh(mesh)) return 0;

  mesh->BakeAnimation(fade,metamorph);
  return mesh;
}

GenMinMesh * __stdcall MinMesh_BoneChain(GenMinMesh *mesh,sF323 p0f,sF323 p1f,sInt count,sInt flags)
{
  GenMinVert *vp;
  GenMinCluster *cl;
  sVector p0,p1,dir,diff;
  sAABox box;

  if(CheckMinMesh(mesh)) return 0;

  for(sInt i=1;i<mesh->Clusters.Count;i++)
  {
    cl = &mesh->Clusters[i];
    cl->AnimType = 2;
  }

  if(flags & 1)
  {
    mesh->CalcBBox(box);
    p0.Init(0,0,box.Min.z);
    p1.Init(0,0,box.Max.z);
  }
  else
  {
    p0.Init(p0f.x,p0f.y,p0f.z,1);
    p1.Init(p1f.x,p1f.y,p1f.z,1);
  }

  dir.Sub4(p1,p0);
  dir.Scale3(1.0f/dir.Dot3(dir));

  mesh->CreateAnimation(count);
  for(sInt i=0;i<count;i++)
  {
    mesh->Animation->Matrices[i].Temp.Init();
    mesh->Animation->Matrices[i].Temp.l.Lin4(p0,p1,(1.0f*i)/(count-1));
    mesh->Animation->Matrices[i].BasePose.Invert(mesh->Animation->Matrices[i].Temp);
    mesh->Animation->Matrices[i].NoAnimation = mesh->Animation->Matrices[i].Temp;
  }

  for(sInt i=0;i<mesh->Vertices.Count;i++)
  {
    vp = &mesh->Vertices[i];
    diff.Init(vp->Pos.x-p0.x,vp->Pos.x-p0.y,vp->Pos.z-p0.z,0);
    sF32 fade = dir.Dot3(diff)*(count-1);

    sInt fi = sRange(sInt(fade*1024),count*1024-1,0);
    sInt index = fi/1024;
    fade = (fi&1023)/1024.0f;
    if(index<0)
    {
      vp->BoneCount = 1;
      vp->Matrix[0] = 0;
      vp->Weights[0] = 1.0f;
    }
    else if(index+1<count)
    {
      vp->BoneCount = 4;
      vp->Matrix[0] = sMax(0,index-1);
      vp->Matrix[1] = index;
      vp->Matrix[2] = sMin(count-1,index+1);
      vp->Matrix[3] = sMin(count-1,index+2);

      sF32 f1 = fade;
      sF32 f2 = fade*fade;
      sF32 f3 = fade*fade*fade;
      vp->Weights[0] = -0.5f*f3 +1.0f*f2 -0.5f*f1    ;
      vp->Weights[1] =  1.5f*f3 -2.5f*f2           +1;
      vp->Weights[2] = -1.5f*f3 +2.0f*f2 +0.5f*f1    ;
      vp->Weights[3] =  0.5f*f3 -0.5f*f2             ;
    }
    else
    {
      vp->BoneCount = 1;
      vp->Matrix[0] = count-1;
      vp->Weights[0] = 1.0f;
    }
  }

  return mesh;
}

GenMinMesh * __stdcall MinMesh_BoneTrain(GenMinMesh *mesh,GenSpline *spline,sF32 delta,sInt mode,sF32 offset)
{
  if(CheckMinMesh(mesh)) { sRelease(spline); return 0; }
  if(!spline) return mesh;
  if(mesh->Animation)
  {
    for(sInt i=0;i<mesh->Animation->Matrices.Count;i++)
    {
      GenMinMatrix *mat = &mesh->Animation->Matrices[i];
      sF32 time = i*delta/(mesh->Animation->Matrices.Count-1)+offset;

      sRelease(mat->Spline);
      mat->Spline = spline;
      mat->Spline->AddRef();
      switch(mode&3)
      {
      case 0:
        mat->Offset = time;
        mat->Factor = 1;
        mat->Spread = 0;
        break;
      case 1:
        mat->Offset = 0;
        mat->Factor = time;
        mat->Spread = 0;
        break;
      case 2:
        mat->Offset = 0;
        mat->Factor = 0;
        mat->Spread = 1;
        break;
      }
    }
  }
  sRelease(spline);
  return mesh;
}

GenMinMesh * __stdcall MinMesh_ScaleAnim(GenMinMesh *mesh,sF32 scale)
{
  if(CheckMinMesh(mesh)) return 0;

  if(mesh->Animation)
  {
    for(sInt i=0;i<mesh->Animation->Matrices.Count;i++)
    {
      GenMinMatrix *mm = &mesh->Animation->Matrices[i];
      mm->BasePose.l.Scale3(scale);      
      mm->NoAnimation.l.Scale3(scale);

      if(mm->Spline)
      {
        BlobSpline *bs = mm->Spline->GetBlobSpline();
        for(sInt i=0;i<bs->Count;i++)
        {
          bs->Keys[i].px *= scale;
          bs->Keys[i].py *= scale;
          bs->Keys[i].pz *= scale;
        }
      }
      mm->SRT[6] *= scale;
      mm->SRT[7] *= scale;
      mm->SRT[8] *= scale;
      if(mm->TPtr)
      {
        for(sInt i=0;i<mm->KeyCount*3;i++)
          mm->TPtr[i] *= scale;
      }
    } 
  }

  sMatrix m;
  m.Init();
  m.i.x = scale;
  m.j.y = scale;
  m.k.z = scale;
  mesh->Transform(MMU_ALL,m);

  return mesh;
}

/****************************************************************************/
/***                                                                      ***/
/***   Marching Tetrahedra                                                ***/
/***                                                                      ***/
/****************************************************************************/
 
#if !sINTRO
enum
{
  MTShift = 4,
  MTGrid = 1<<MTShift,
  MTMask = MTGrid-1,
  MTYStep = MTGrid,
  MTZStep = MTYStep*MTGrid,
  MTMaxVert = 65535,
  // this is the biggest you can make the grid without changing data types!
};

static sInt MTHash[1024];
static sInt MTData[MTMaxVert*2];
static sF32 MTDensity[MTGrid*MTGrid*MTGrid];

// marching cubes tables coming up!
static const sU16 MTEdgeMaskTable[256]=
{
  0x000,0x109,0x203,0x30a,0x406,0x50f,0x605,0x70c,0x80c,0x905,0xa0f,0xb06,0xc0a,0xd03,0xe09,0xf00,
  0x190,0x099,0x393,0x29a,0x596,0x49f,0x795,0x69c,0x99c,0x895,0xb9f,0xa96,0xd9a,0xc93,0xf99,0xe90,
  0x230,0x339,0x033,0x13a,0x636,0x73f,0x435,0x53c,0xa3c,0xb35,0x83f,0x936,0xe3a,0xf33,0xc39,0xd30,
  0x3a0,0x2a9,0x1a3,0x0aa,0x7a6,0x6af,0x5a5,0x4ac,0xbac,0xaa5,0x9af,0x8a6,0xfaa,0xea3,0xda9,0xca0,
  0x460,0x569,0x663,0x76a,0x066,0x16f,0x265,0x36c,0xc6c,0xd65,0xe6f,0xf66,0x86a,0x963,0xa69,0xb60,
  0x5f0,0x4f9,0x7f3,0x6fa,0x1f6,0x0ff,0x3f5,0x2fc,0xdfc,0xcf5,0xfff,0xef6,0x9fa,0x8f3,0xbf9,0xaf0,
  0x650,0x759,0x453,0x55a,0x256,0x35f,0x055,0x15c,0xe5c,0xf55,0xc5f,0xd56,0xa5a,0xb53,0x859,0x950,
  0x7c0,0x6c9,0x5c3,0x4ca,0x3c6,0x2cf,0x1c5,0x0cc,0xfcc,0xec5,0xdcf,0xcc6,0xbca,0xac3,0x9c9,0x8c0,
  0x8c0,0x9c9,0xac3,0xbca,0xcc6,0xdcf,0xec5,0xfcc,0x0cc,0x1c5,0x2cf,0x3c6,0x4ca,0x5c3,0x6c9,0x7c0,
  0x950,0x859,0xb53,0xa5a,0xd56,0xc5f,0xf55,0xe5c,0x15c,0x055,0x35f,0x256,0x55a,0x453,0x759,0x650,
  0xaf0,0xbf9,0x8f3,0x9fa,0xef6,0xfff,0xcf5,0xdfc,0x2fc,0x3f5,0x0ff,0x1f6,0x6fa,0x7f3,0x4f9,0x5f0,
  0xb60,0xa69,0x963,0x86a,0xf66,0xe6f,0xd65,0xc6c,0x36c,0x265,0x16f,0x066,0x76a,0x663,0x569,0x460,
  0xca0,0xda9,0xea3,0xfaa,0x8a6,0x9af,0xaa5,0xbac,0x4ac,0x5a5,0x6af,0x7a6,0x0aa,0x1a3,0x2a9,0x3a0,
  0xd30,0xc39,0xf33,0xe3a,0x936,0x83f,0xb35,0xa3c,0x53c,0x435,0x73f,0x636,0x13a,0x033,0x339,0x230,
  0xe90,0xf99,0xc93,0xd9a,0xa96,0xb9f,0x895,0x99c,0x69c,0x795,0x49f,0x596,0x29a,0x393,0x099,0x190,
  0xf00,0xe09,0xd03,0xc0a,0xb06,0xa0f,0x905,0x80c,0x70c,0x605,0x50f,0x406,0x30a,0x203,0x109,0x000
};

static const sU8 MTTriTable[256][16] =  
{
  {12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12},
  { 0, 8, 3,12,12,12,12,12,12,12,12,12,12,12,12,12},
  { 0, 1, 9,12,12,12,12,12,12,12,12,12,12,12,12,12},
  { 1, 8, 3, 9, 8, 1,12,12,12,12,12,12,12,12,12,12},
  { 1, 2,10,12,12,12,12,12,12,12,12,12,12,12,12,12},
  { 0, 8, 3, 1, 2,10,12,12,12,12,12,12,12,12,12,12},
  { 9, 2,10, 0, 2, 9,12,12,12,12,12,12,12,12,12,12},
  { 2, 8, 3, 2,10, 8,10, 9, 8,12,12,12,12,12,12,12},
  { 3,11, 2,12,12,12,12,12,12,12,12,12,12,12,12,12},
  { 0,11, 2, 8,11, 0,12,12,12,12,12,12,12,12,12,12},
  { 1, 9, 0, 2, 3,11,12,12,12,12,12,12,12,12,12,12},
  { 1,11, 2, 1, 9,11, 9, 8,11,12,12,12,12,12,12,12},
  { 3,10, 1,11,10, 3,12,12,12,12,12,12,12,12,12,12},
  { 0,10, 1, 0, 8,10, 8,11,10,12,12,12,12,12,12,12},
  { 3, 9, 0, 3,11, 9,11,10, 9,12,12,12,12,12,12,12},
  { 9, 8,10,10, 8,11,12,12,12,12,12,12,12,12,12,12},
  { 4, 7, 8,12,12,12,12,12,12,12,12,12,12,12,12,12},
  { 4, 3, 0, 7, 3, 4,12,12,12,12,12,12,12,12,12,12},
  { 0, 1, 9, 8, 4, 7,12,12,12,12,12,12,12,12,12,12},
  { 4, 1, 9, 4, 7, 1, 7, 3, 1,12,12,12,12,12,12,12},
  { 1, 2,10, 8, 4, 7,12,12,12,12,12,12,12,12,12,12},
  { 3, 4, 7, 3, 0, 4, 1, 2,10,12,12,12,12,12,12,12},
  { 9, 2,10, 9, 0, 2, 8, 4, 7,12,12,12,12,12,12,12},
  { 2,10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4,12,12,12,12},
  { 8, 4, 7, 3,11, 2,12,12,12,12,12,12,12,12,12,12},
  {11, 4, 7,11, 2, 4, 2, 0, 4,12,12,12,12,12,12,12},
  { 9, 0, 1, 8, 4, 7, 2, 3,11,12,12,12,12,12,12,12},
  { 4, 7,11, 9, 4,11, 9,11, 2, 9, 2, 1,12,12,12,12},
  { 3,10, 1, 3,11,10, 7, 8, 4,12,12,12,12,12,12,12},
  { 1,11,10, 1, 4,11, 1, 0, 4, 7,11, 4,12,12,12,12},
  { 4, 7, 8, 9, 0,11, 9,11,10,11, 0, 3,12,12,12,12},
  { 4, 7,11, 4,11, 9, 9,11,10,12,12,12,12,12,12,12},
  { 9, 5, 4,12,12,12,12,12,12,12,12,12,12,12,12,12},
  { 9, 5, 4, 0, 8, 3,12,12,12,12,12,12,12,12,12,12},
  { 0, 5, 4, 1, 5, 0,12,12,12,12,12,12,12,12,12,12},
  { 8, 5, 4, 8, 3, 5, 3, 1, 5,12,12,12,12,12,12,12},
  { 1, 2,10, 9, 5, 4,12,12,12,12,12,12,12,12,12,12},
  { 3, 0, 8, 1, 2,10, 4, 9, 5,12,12,12,12,12,12,12},
  { 5, 2,10, 5, 4, 2, 4, 0, 2,12,12,12,12,12,12,12},
  { 2,10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8,12,12,12,12},
  { 9, 5, 4, 2, 3,11,12,12,12,12,12,12,12,12,12,12},
  { 0,11, 2, 0, 8,11, 4, 9, 5,12,12,12,12,12,12,12},
  { 0, 5, 4, 0, 1, 5, 2, 3,11,12,12,12,12,12,12,12},
  { 2, 1, 5, 2, 5, 8, 2, 8,11, 4, 8, 5,12,12,12,12},
  {10, 3,11,10, 1, 3, 9, 5, 4,12,12,12,12,12,12,12},
  { 4, 9, 5, 0, 8, 1, 8,10, 1, 8,11,10,12,12,12,12},
  { 5, 4, 0, 5, 0,11, 5,11,10,11, 0, 3,12,12,12,12},
  { 5, 4, 8, 5, 8,10,10, 8,11,12,12,12,12,12,12,12},
  { 9, 7, 8, 5, 7, 9,12,12,12,12,12,12,12,12,12,12},
  { 9, 3, 0, 9, 5, 3, 5, 7, 3,12,12,12,12,12,12,12},
  { 0, 7, 8, 0, 1, 7, 1, 5, 7,12,12,12,12,12,12,12},
  { 1, 5, 3, 3, 5, 7,12,12,12,12,12,12,12,12,12,12},
  { 9, 7, 8, 9, 5, 7,10, 1, 2,12,12,12,12,12,12,12},
  {10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3,12,12,12,12},
  { 8, 0, 2, 8, 2, 5, 8, 5, 7,10, 5, 2,12,12,12,12},
  { 2,10, 5, 2, 5, 3, 3, 5, 7,12,12,12,12,12,12,12},
  { 7, 9, 5, 7, 8, 9, 3,11, 2,12,12,12,12,12,12,12},
  { 9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7,11,12,12,12,12},
  { 2, 3,11, 0, 1, 8, 1, 7, 8, 1, 5, 7,12,12,12,12},
  {11, 2, 1,11, 1, 7, 7, 1, 5,12,12,12,12,12,12,12},
  { 9, 5, 8, 8, 5, 7,10, 1, 3,10, 3,11,12,12,12,12},
  { 5, 7, 0, 5, 0, 9, 7,11, 0, 1, 0,10,11,10, 0,12},
  {11,10, 0,11, 0, 3,10, 5, 0, 8, 0, 7, 5, 7, 0,12},
  {11,10, 5, 7,11, 5,12,12,12,12,12,12,12,12,12,12},
  {10, 6, 5,12,12,12,12,12,12,12,12,12,12,12,12,12},
  { 0, 8, 3, 5,10, 6,12,12,12,12,12,12,12,12,12,12},
  { 9, 0, 1, 5,10, 6,12,12,12,12,12,12,12,12,12,12},
  { 1, 8, 3, 1, 9, 8, 5,10, 6,12,12,12,12,12,12,12},
  { 1, 6, 5, 2, 6, 1,12,12,12,12,12,12,12,12,12,12},
  { 1, 6, 5, 1, 2, 6, 3, 0, 8,12,12,12,12,12,12,12},
  { 9, 6, 5, 9, 0, 6, 0, 2, 6,12,12,12,12,12,12,12},
  { 5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8,12,12,12,12},
  { 2, 3,11,10, 6, 5,12,12,12,12,12,12,12,12,12,12},
  {11, 0, 8,11, 2, 0,10, 6, 5,12,12,12,12,12,12,12},
  { 0, 1, 9, 2, 3,11, 5,10, 6,12,12,12,12,12,12,12},
  { 5,10, 6, 1, 9, 2, 9,11, 2, 9, 8,11,12,12,12,12},
  { 6, 3,11, 6, 5, 3, 5, 1, 3,12,12,12,12,12,12,12},
  { 0, 8,11, 0,11, 5, 0, 5, 1, 5,11, 6,12,12,12,12},
  { 3,11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9,12,12,12,12},
  { 6, 5, 9, 6, 9,11,11, 9, 8,12,12,12,12,12,12,12},
  { 5,10, 6, 4, 7, 8,12,12,12,12,12,12,12,12,12,12},
  { 4, 3, 0, 4, 7, 3, 6, 5,10,12,12,12,12,12,12,12},
  { 1, 9, 0, 5,10, 6, 8, 4, 7,12,12,12,12,12,12,12},
  {10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4,12,12,12,12},
  { 6, 1, 2, 6, 5, 1, 4, 7, 8,12,12,12,12,12,12,12},
  { 1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7,12,12,12,12},
  { 8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6,12,12,12,12},
  { 7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9,12},
  { 3,11, 2, 7, 8, 4,10, 6, 5,12,12,12,12,12,12,12},
  { 5,10, 6, 4, 7, 2, 4, 2, 0, 2, 7,11,12,12,12,12},
  { 0, 1, 9, 4, 7, 8, 2, 3,11, 5,10, 6,12,12,12,12},
  { 9, 2, 1, 9,11, 2, 9, 4,11, 7,11, 4, 5,10, 6,12},
  { 8, 4, 7, 3,11, 5, 3, 5, 1, 5,11, 6,12,12,12,12},
  { 5, 1,11, 5,11, 6, 1, 0,11, 7,11, 4, 0, 4,11,12},
  { 0, 5, 9, 0, 6, 5, 0, 3, 6,11, 6, 3, 8, 4, 7,12},
  { 6, 5, 9, 6, 9,11, 4, 7, 9, 7,11, 9,12,12,12,12},
  {10, 4, 9, 6, 4,10,12,12,12,12,12,12,12,12,12,12},
  { 4,10, 6, 4, 9,10, 0, 8, 3,12,12,12,12,12,12,12},
  {10, 0, 1,10, 6, 0, 6, 4, 0,12,12,12,12,12,12,12},
  { 8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1,10,12,12,12,12},
  { 1, 4, 9, 1, 2, 4, 2, 6, 4,12,12,12,12,12,12,12},
  { 3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4,12,12,12,12},
  { 0, 2, 4, 4, 2, 6,12,12,12,12,12,12,12,12,12,12},
  { 8, 3, 2, 8, 2, 4, 4, 2, 6,12,12,12,12,12,12,12},
  {10, 4, 9,10, 6, 4,11, 2, 3,12,12,12,12,12,12,12},
  { 0, 8, 2, 2, 8,11, 4, 9,10, 4,10, 6,12,12,12,12},
  { 3,11, 2, 0, 1, 6, 0, 6, 4, 6, 1,10,12,12,12,12},
  { 6, 4, 1, 6, 1,10, 4, 8, 1, 2, 1,11, 8,11, 1,12},
  { 9, 6, 4, 9, 3, 6, 9, 1, 3,11, 6, 3,12,12,12,12},
  { 8,11, 1, 8, 1, 0,11, 6, 1, 9, 1, 4, 6, 4, 1,12},
  { 3,11, 6, 3, 6, 0, 0, 6, 4,12,12,12,12,12,12,12},
  { 6, 4, 8,11, 6, 8,12,12,12,12,12,12,12,12,12,12},
  { 7,10, 6, 7, 8,10, 8, 9,10,12,12,12,12,12,12,12},
  { 0, 7, 3, 0,10, 7, 0, 9,10, 6, 7,10,12,12,12,12},
  {10, 6, 7, 1,10, 7, 1, 7, 8, 1, 8, 0,12,12,12,12},
  {10, 6, 7,10, 7, 1, 1, 7, 3,12,12,12,12,12,12,12},
  { 1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7,12,12,12,12},
  { 2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9,12},
  { 7, 8, 0, 7, 0, 6, 6, 0, 2,12,12,12,12,12,12,12},
  { 7, 3, 2, 6, 7, 2,12,12,12,12,12,12,12,12,12,12},
  { 2, 3,11,10, 6, 8,10, 8, 9, 8, 6, 7,12,12,12,12},
  { 2, 0, 7, 2, 7,11, 0, 9, 7, 6, 7,10, 9,10, 7,12},
  { 1, 8, 0, 1, 7, 8, 1,10, 7, 6, 7,10, 2, 3,11,12},
  {11, 2, 1,11, 1, 7,10, 6, 1, 6, 7, 1,12,12,12,12},
  { 8, 9, 6, 8, 6, 7, 9, 1, 6,11, 6, 3, 1, 3, 6,12},
  { 0, 9, 1,11, 6, 7,12,12,12,12,12,12,12,12,12,12},
  { 7, 8, 0, 7, 0, 6, 3,11, 0,11, 6, 0,12,12,12,12},
  { 7,11, 6,12,12,12,12,12,12,12,12,12,12,12,12,12},
  { 7, 6,11,12,12,12,12,12,12,12,12,12,12,12,12,12},
  { 3, 0, 8,11, 7, 6,12,12,12,12,12,12,12,12,12,12},
  { 0, 1, 9,11, 7, 6,12,12,12,12,12,12,12,12,12,12},
  { 8, 1, 9, 8, 3, 1,11, 7, 6,12,12,12,12,12,12,12},
  {10, 1, 2, 6,11, 7,12,12,12,12,12,12,12,12,12,12},
  { 1, 2,10, 3, 0, 8, 6,11, 7,12,12,12,12,12,12,12},
  { 2, 9, 0, 2,10, 9, 6,11, 7,12,12,12,12,12,12,12},
  { 6,11, 7, 2,10, 3,10, 8, 3,10, 9, 8,12,12,12,12},
  { 7, 2, 3, 6, 2, 7,12,12,12,12,12,12,12,12,12,12},
  { 7, 0, 8, 7, 6, 0, 6, 2, 0,12,12,12,12,12,12,12},
  { 2, 7, 6, 2, 3, 7, 0, 1, 9,12,12,12,12,12,12,12},
  { 1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6,12,12,12,12},
  {10, 7, 6,10, 1, 7, 1, 3, 7,12,12,12,12,12,12,12},
  {10, 7, 6, 1, 7,10, 1, 8, 7, 1, 0, 8,12,12,12,12},
  { 0, 3, 7, 0, 7,10, 0,10, 9, 6,10, 7,12,12,12,12},
  { 7, 6,10, 7,10, 8, 8,10, 9,12,12,12,12,12,12,12},
  { 6, 8, 4,11, 8, 6,12,12,12,12,12,12,12,12,12,12},
  { 3, 6,11, 3, 0, 6, 0, 4, 6,12,12,12,12,12,12,12},
  { 8, 6,11, 8, 4, 6, 9, 0, 1,12,12,12,12,12,12,12},
  { 9, 4, 6, 9, 6, 3, 9, 3, 1,11, 3, 6,12,12,12,12},
  { 6, 8, 4, 6,11, 8, 2,10, 1,12,12,12,12,12,12,12},
  { 1, 2,10, 3, 0,11, 0, 6,11, 0, 4, 6,12,12,12,12},
  { 4,11, 8, 4, 6,11, 0, 2, 9, 2,10, 9,12,12,12,12},
  {10, 9, 3,10, 3, 2, 9, 4, 3,11, 3, 6, 4, 6, 3,12},
  { 8, 2, 3, 8, 4, 2, 4, 6, 2,12,12,12,12,12,12,12},
  { 0, 4, 2, 4, 6, 2,12,12,12,12,12,12,12,12,12,12},
  { 1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8,12,12,12,12},
  { 1, 9, 4, 1, 4, 2, 2, 4, 6,12,12,12,12,12,12,12},
  { 8, 1, 3, 8, 6, 1, 8, 4, 6, 6,10, 1,12,12,12,12},
  {10, 1, 0,10, 0, 6, 6, 0, 4,12,12,12,12,12,12,12},
  { 4, 6, 3, 4, 3, 8, 6,10, 3, 0, 3, 9,10, 9, 3,12},
  {10, 9, 4, 6,10, 4,12,12,12,12,12,12,12,12,12,12},
  { 4, 9, 5, 7, 6,11,12,12,12,12,12,12,12,12,12,12},
  { 0, 8, 3, 4, 9, 5,11, 7, 6,12,12,12,12,12,12,12},
  { 5, 0, 1, 5, 4, 0, 7, 6,11,12,12,12,12,12,12,12},
  {11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5,12,12,12,12},
  { 9, 5, 4,10, 1, 2, 7, 6,11,12,12,12,12,12,12,12},
  { 6,11, 7, 1, 2,10, 0, 8, 3, 4, 9, 5,12,12,12,12},
  { 7, 6,11, 5, 4,10, 4, 2,10, 4, 0, 2,12,12,12,12},
  { 3, 4, 8, 3, 5, 4, 3, 2, 5,10, 5, 2,11, 7, 6,12},
  { 7, 2, 3, 7, 6, 2, 5, 4, 9,12,12,12,12,12,12,12},
  { 9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7,12,12,12,12},
  { 3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0,12,12,12,12},
  { 6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8,12},
  { 9, 5, 4,10, 1, 6, 1, 7, 6, 1, 3, 7,12,12,12,12},
  { 1, 6,10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4,12},
  { 4, 0,10, 4,10, 5, 0, 3,10, 6,10, 7, 3, 7,10,12},
  { 7, 6,10, 7,10, 8, 5, 4,10, 4, 8,10,12,12,12,12},
  { 6, 9, 5, 6,11, 9,11, 8, 9,12,12,12,12,12,12,12},
  { 3, 6,11, 0, 6, 3, 0, 5, 6, 0, 9, 5,12,12,12,12},
  { 0,11, 8, 0, 5,11, 0, 1, 5, 5, 6,11,12,12,12,12},
  { 6,11, 3, 6, 3, 5, 5, 3, 1,12,12,12,12,12,12,12},
  { 1, 2,10, 9, 5,11, 9,11, 8,11, 5, 6,12,12,12,12},
  { 0,11, 3, 0, 6,11, 0, 9, 6, 5, 6, 9, 1, 2,10,12},
  {11, 8, 5,11, 5, 6, 8, 0, 5,10, 5, 2, 0, 2, 5,12},
  { 6,11, 3, 6, 3, 5, 2,10, 3,10, 5, 3,12,12,12,12},
  { 5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2,12,12,12,12},
  { 9, 5, 6, 9, 6, 0, 0, 6, 2,12,12,12,12,12,12,12},
  { 1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8,12},
  { 1, 5, 6, 2, 1, 6,12,12,12,12,12,12,12,12,12,12},
  { 1, 3, 6, 1, 6,10, 3, 8, 6, 5, 6, 9, 8, 9, 6,12},
  {10, 1, 0,10, 0, 6, 9, 5, 0, 5, 6, 0,12,12,12,12},
  { 0, 3, 8, 5, 6,10,12,12,12,12,12,12,12,12,12,12},
  {10, 5, 6,12,12,12,12,12,12,12,12,12,12,12,12,12},
  {11, 5,10, 7, 5,11,12,12,12,12,12,12,12,12,12,12},
  {11, 5,10,11, 7, 5, 8, 3, 0,12,12,12,12,12,12,12},
  { 5,11, 7, 5,10,11, 1, 9, 0,12,12,12,12,12,12,12},
  {10, 7, 5,10,11, 7, 9, 8, 1, 8, 3, 1,12,12,12,12},
  {11, 1, 2,11, 7, 1, 7, 5, 1,12,12,12,12,12,12,12},
  { 0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2,11,12,12,12,12},
  { 9, 7, 5, 9, 2, 7, 9, 0, 2, 2,11, 7,12,12,12,12},
  { 7, 5, 2, 7, 2,11, 5, 9, 2, 3, 2, 8, 9, 8, 2,12},
  { 2, 5,10, 2, 3, 5, 3, 7, 5,12,12,12,12,12,12,12},
  { 8, 2, 0, 8, 5, 2, 8, 7, 5,10, 2, 5,12,12,12,12},
  { 9, 0, 1, 5,10, 3, 5, 3, 7, 3,10, 2,12,12,12,12},
  { 9, 8, 2, 9, 2, 1, 8, 7, 2,10, 2, 5, 7, 5, 2,12},
  { 1, 3, 5, 3, 7, 5,12,12,12,12,12,12,12,12,12,12},
  { 0, 8, 7, 0, 7, 1, 1, 7, 5,12,12,12,12,12,12,12},
  { 9, 0, 3, 9, 3, 5, 5, 3, 7,12,12,12,12,12,12,12},
  { 9, 8, 7, 5, 9, 7,12,12,12,12,12,12,12,12,12,12},
  { 5, 8, 4, 5,10, 8,10,11, 8,12,12,12,12,12,12,12},
  { 5, 0, 4, 5,11, 0, 5,10,11,11, 3, 0,12,12,12,12},
  { 0, 1, 9, 8, 4,10, 8,10,11,10, 4, 5,12,12,12,12},
  {10,11, 4,10, 4, 5,11, 3, 4, 9, 4, 1, 3, 1, 4,12},
  { 2, 5, 1, 2, 8, 5, 2,11, 8, 4, 5, 8,12,12,12,12},
  { 0, 4,11, 0,11, 3, 4, 5,11, 2,11, 1, 5, 1,11,12},
  { 0, 2, 5, 0, 5, 9, 2,11, 5, 4, 5, 8,11, 8, 5,12},
  { 9, 4, 5, 2,11, 3,12,12,12,12,12,12,12,12,12,12},
  { 2, 5,10, 3, 5, 2, 3, 4, 5, 3, 8, 4,12,12,12,12},
  { 5,10, 2, 5, 2, 4, 4, 2, 0,12,12,12,12,12,12,12},
  { 3,10, 2, 3, 5,10, 3, 8, 5, 4, 5, 8, 0, 1, 9,12},
  { 5,10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2,12,12,12,12},
  { 8, 4, 5, 8, 5, 3, 3, 5, 1,12,12,12,12,12,12,12},
  { 0, 4, 5, 1, 0, 5,12,12,12,12,12,12,12,12,12,12},
  { 8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5,12,12,12,12},
  { 9, 4, 5,12,12,12,12,12,12,12,12,12,12,12,12,12},
  { 4,11, 7, 4, 9,11, 9,10,11,12,12,12,12,12,12,12},
  { 0, 8, 3, 4, 9, 7, 9,11, 7, 9,10,11,12,12,12,12},
  { 1,10,11, 1,11, 4, 1, 4, 0, 7, 4,11,12,12,12,12},
  { 3, 1, 4, 3, 4, 8, 1,10, 4, 7, 4,11,10,11, 4,12},
  { 4,11, 7, 9,11, 4, 9, 2,11, 9, 1, 2,12,12,12,12},
  { 9, 7, 4, 9,11, 7, 9, 1,11, 2,11, 1, 0, 8, 3,12},
  {11, 7, 4,11, 4, 2, 2, 4, 0,12,12,12,12,12,12,12},
  {11, 7, 4,11, 4, 2, 8, 3, 4, 3, 2, 4,12,12,12,12},
  { 2, 9,10, 2, 7, 9, 2, 3, 7, 7, 4, 9,12,12,12,12},
  { 9,10, 7, 9, 7, 4,10, 2, 7, 8, 7, 0, 2, 0, 7,12},
  { 3, 7,10, 3,10, 2, 7, 4,10, 1,10, 0, 4, 0,10,12},
  { 1,10, 2, 8, 7, 4,12,12,12,12,12,12,12,12,12,12},
  { 4, 9, 1, 4, 1, 7, 7, 1, 3,12,12,12,12,12,12,12},
  { 4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1,12,12,12,12},
  { 4, 0, 3, 7, 4, 3,12,12,12,12,12,12,12,12,12,12},
  { 4, 8, 7,12,12,12,12,12,12,12,12,12,12,12,12,12},
  { 9,10, 8,10,11, 8,12,12,12,12,12,12,12,12,12,12},
  { 3, 0, 9, 3, 9,11,11, 9,10,12,12,12,12,12,12,12},
  { 0, 1,10, 0,10, 8, 8,10,11,12,12,12,12,12,12,12},
  { 3, 1,10,11, 3,10,12,12,12,12,12,12,12,12,12,12},
  { 1, 2,11, 1,11, 9, 9,11, 8,12,12,12,12,12,12,12},
  { 3, 0, 9, 3, 9,11, 1, 2, 9, 2,11, 9,12,12,12,12},
  { 0, 2,11, 8, 0,11,12,12,12,12,12,12,12,12,12,12},
  { 3, 2,11,12,12,12,12,12,12,12,12,12,12,12,12,12},
  { 2, 3, 8, 2, 8,10,10, 8, 9,12,12,12,12,12,12,12},
  { 9,10, 2, 0, 9, 2,12,12,12,12,12,12,12,12,12,12},
  { 2, 3, 8, 2, 8,10, 0, 1, 8, 1,10, 8,12,12,12,12},
  { 1,10, 2,12,12,12,12,12,12,12,12,12,12,12,12,12},
  { 1, 3, 8, 9, 1, 8,12,12,12,12,12,12,12,12,12,12},
  { 0, 9, 1,12,12,12,12,12,12,12,12,12,12,12,12,12},
  { 0, 3, 8,12,12,12,12,12,12,12,12,12,12,12,12,12},
  {12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12}
};

static void MTProcessCell(GenMinMesh *mesh,sInt pos,sF32 isoValue)
{
  enum { X = 1, Y = MTYStep, Z = MTZStep };

  static const sInt Step[8] = {
    0+0+0,0+0+X,0+Y+X,0+Y+0,Z+0+0,Z+0+X,Z+Y+X,Z+Y+0
  };

  static const sInt EdgeOffs[2][12] = {
    { 0+0+0,0+0+X,0+Y+X,0+Y+0,Z+0+0,Z+0+X,Z+Y+X,Z+Y+0,0+0+0,0+0+X,0+Y+X,0+Y+0 },
    { 0+0+X,0+Y+X,0+Y+0,0+0+0,Z+0+X,Z+Y+X,Z+Y+0,Z+0+0,Z+0+0,Z+0+X,Z+Y+X,Z+Y+0 }
  };

  static const sF32 EdgeStartC[3][12] = {
    { 0,1,1,0,0,1,1,0,0,1,1,0 },
    { 0,0,1,1,0,0,1,1,0,0,1,1 },
    { 0,0,0,0,1,1,1,1,0,0,0,0 }
  };

  static const sF32 EdgeDirC[3][12] = {
    {  1, 0,-1, 0, 1, 0,-1, 0, 0, 0, 0, 0 },
    {  0, 1, 0,-1, 0, 1, 0,-1, 0, 0, 0, 0 },
    {  0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }
  };

  static const sInt EdgeCode[12] = {
    (0+0+0)*4+0,(0+0+X)*4+1,(0+Y+0)*4+0,(0+0+0)*4+1,
    (Z+0+0)*4+0,(Z+0+X)*4+1,(Z+Y+0)*4+0,(Z+0+0)*4+1,
    (0+0+0)*4+2,(0+0+X)*4+2,(0+Y+X)*4+2,(0+Y+0)*4+2
  };

  // calc vertex flags
  sInt mask = 0;
  for(sInt i=0,tmask=1;i<8;i++,tmask+=tmask)
    mask |= MTDensity[pos + Step[i]] < isoValue ? tmask : 0;

  // get edge flags
  sInt edgeMask = MTEdgeMaskTable[mask];
  if(!edgeMask)
    return;

  // calc actual position and lookup base
  sInt lookupBase = pos << 2;
  sInt posx = pos & MTMask;
  sInt posy = (pos >> MTShift) & MTMask;
  sInt posz = (pos >> (2*MTShift)) & MTMask;

  // calc edge vertices
  sInt edgeVert[12];
  for(sInt i=0,tmask=edgeMask;i<12;i++,tmask>>=1)
  {
    if((tmask & 1) == 0)
      continue;

    sInt lookupCode = lookupBase + EdgeCode[i];
    sInt bucket = lookupCode & 1023;
    sInt v = -1;

    // lookup vertex in hash table
    for(sInt h=MTHash[bucket];h!=-1;h=MTData[h*2+1])
    {
      if(MTData[h*2] == lookupCode)
      {
        v = h;
        break;
      }
    }

    // not found
    if(v == -1)
    {
      // alloc new vertex
      v = mesh->Vertices.Count++;
      MTData[v*2+0] = lookupCode;
      MTData[v*2+1] = MTHash[bucket];
      MTHash[bucket] = v;

      // calc intersection t
      sF32 d0 = MTDensity[pos + EdgeOffs[0][i]];
      sF32 d1 = MTDensity[pos + EdgeOffs[1][i]];
      sF32 t = (isoValue - d0) / (d1 - d0);
      sVERIFY(t >= 0.0f && t <= 1.0f);

      // calc vertex position
      GenMinVert *vp = &mesh->Vertices[v];
      vp->Pos.x = posx + EdgeStartC[0][i] + t * EdgeDirC[0][i];
      vp->Pos.y = posy + EdgeStartC[1][i] + t * EdgeDirC[1][i];
      vp->Pos.z = posz + EdgeStartC[2][i] + t * EdgeDirC[2][i];
    }

    // store vertex index
    edgeVert[i] = v;
  }

  // create tris
  GenMinFace *face = &mesh->Faces[mesh->Faces.Count];
  sInt ctr = 0;

  for(const sU8 *tris=MTTriTable[mask];*tris != 12;tris++)
  {
    face->Vertices[ctr] = edgeVert[*tris];
    if(++ctr == 3)
    {
      mesh->Faces.Count++;
      face++;
      ctr = 0;
    }
  }
}

GenMinMesh * __stdcall MinMesh_MTetra(sF32 isoValue)
{
  // create mesh, set up vertices and faces as necessary
  GenMinMesh *mesh = new GenMinMesh;
  mesh->Vertices.AtLeast(MTMaxVert);
  mesh->Faces.AtLeast(MTMaxVert*2);

  sSetMem(&mesh->Vertices[0],0,sizeof(GenMinVert)*mesh->Vertices.Alloc);
  for(sInt i=0;i<mesh->Faces.Alloc;i++)
  {
    GenMinFace *face = &mesh->Faces[i];

    face->Select = 0;
    face->Count = 3;
    face->Cluster = 1;
    face->Flags = 0;
  }

  // clear hash
  sSetMem(MTHash,0xff,sizeof(MTHash));

  // calc density field
  for(sInt z=0;z<MTGrid;z++)
  {
    for(sInt y=0;y<MTGrid;y++)
    {
      for(sInt x=0;x<MTGrid;x++)
      {
        sF32 dx,dy,dz,rsq,dens;

        dens = 0.0f;

        dx = x - 8.0f;
        dy = y - 8.0f;
        dz = z - 8.0f;
        rsq = dx*dx + dy*dy + dz*dz;
        dens += 1.0f / rsq;

        MTDensity[x + y*MTYStep + z*MTZStep] = dens;
      }
    }
  }

  // polygonise it!
  for(sInt z=0;z<MTGrid-1;z++)
  {
    for(sInt y=0;y<MTGrid-1;y++)
    {
      for(sInt x=0;x<MTGrid-1;x++)
        MTProcessCell(mesh,x + y*MTYStep + z*MTZStep,isoValue);
    }
  }

  return mesh;
}
#endif

/****************************************************************************/
/***                                                                      ***/
/***   Topology Modifiers                                                 ***/
/***                                                                      ***/
/****************************************************************************/

GenMinMesh * __stdcall MinMesh_Triangulate(GenMinMesh *mesh)
{
  if(CheckMinMesh(mesh)) return 0;

  // calculate new number of faces
  sInt outFaceCount = 0;
  for(sInt i=0;i<mesh->Faces.Count;i++)
  {
    if(mesh->Faces[i].Count <= 3)
      outFaceCount++;
    else
      outFaceCount += mesh->Faces[i].Count - 2;
  }

  // make new face list
  GenMinFace *outFaces = new GenMinFace[outFaceCount];
  GenMinFace *outFace = outFaces;

  for(sInt i=0;i<mesh->Faces.Count;i++)
  {
    GenMinFace *curFace = &mesh->Faces[i];

    if(mesh->Faces[i].Count <= 3)
      *outFace++ = *curFace;
    else
    {
      for(sInt j=2;j<curFace->Count;j++)
      {
        *outFace = *curFace;
        outFace->Count = 3;
        outFace->Vertices[1] = curFace->Vertices[j-1];
        outFace->Vertices[2] = curFace->Vertices[j];
        outFace++;
      }
    }
  }

  // exchange face lists
  delete[] mesh->Faces.Array;
  mesh->Faces.Array = outFaces;
  mesh->Faces.Alloc = mesh->Faces.Count = outFaceCount;

  mesh->ChangeTopo();

  return mesh;
}

static void __stdcall ExtrudeOnce(GenMinMesh *mesh,sInt *groups)
{
  // initialize
  sInt faceCount = mesh->Faces.Count;
  sInt *vertRemap = new sInt[mesh->Vertices.Count];
  for(sInt i=0;i<mesh->Vertices.Count;i++)
    vertRemap[i] = i;

  for(sInt faceInd=0;faceInd<faceCount;faceInd++)
  {
    GenMinFace *curFace = &mesh->Faces[faceInd];
    if(!curFace->Select || curFace->Count <= 2)
      continue;

    // go through halfedges for this face
    sInt count = curFace->Count;
    sInt group = groups[faceInd];

    for(sInt i=0;i<count;i++)
    {
      sInt adjacent = curFace->Adjacent[i] >> 3;
      if(adjacent != -1 && groups[adjacent] == group) // not a boundary
        continue;

      // this edge is a boundary, so extrude a quad
      sInt oldVert[2],newVert[2];

      for(sInt j=0;j<2;j++)
      {
        sInt old = curFace->Vertices[(i + j) % count];

        if(vertRemap[old] == old) // not repositioned yet
        {
          vertRemap[old] = mesh->Vertices.Count;
          GenMinVert *nv = mesh->Vertices.Add();
          *nv = mesh->Vertices[old];
        }

        oldVert[j] = old;
        newVert[j] = vertRemap[old];
      }

      // add the new face
      GenMinFace *newFace = mesh->Faces.Add();
      curFace = &mesh->Faces[faceInd];
      newFace->Select = 0;
      newFace->Count = 4;
      newFace->Cluster = curFace->Cluster;
      newFace->Flags = curFace->Flags;
      newFace->Vertices[0] = oldVert[0];
      newFace->Vertices[1] = oldVert[1];
      newFace->Vertices[2] = newVert[1];
      newFace->Vertices[3] = newVert[0];
      sSetMem(newFace->Adjacent,0xff,sizeof(newFace->Adjacent));
    }
  }

  for(sInt faceInd=0;faceInd<faceCount;faceInd++)
  {
    GenMinFace *curFace = &mesh->Faces[faceInd];
    if(!curFace->Select || curFace->Count <= 2)
      continue;

    for(sInt j=0;j<curFace->Count;j++)
      curFace->Vertices[j] = vertRemap[curFace->Vertices[j]];
  }

  delete[] vertRemap;
}

GenMinMesh * __stdcall MinMesh_Extrude(GenMinMesh *mesh,sInt mode,sInt count,sF323 distance)
{
  if(CheckMinMesh(mesh)) return 0;
  mesh->CalcAdjacency();
  mesh->CalcNormals();

  // prepare
  sF32 angThresh = 0.4995f; // cos(threshold angle)
  sInt origFaceCount = mesh->Faces.Count;
  sInt *faceGroup = new sInt[origFaceCount];
  sInt *groupNext = new sInt[origFaceCount];
  sInt displaceMode = mode & 3;

  // build groups (depth search)
  sSetMem(faceGroup,0xff,origFaceCount * sizeof(sInt));
  sInt groupCount = 0;

  for(sInt i=0;i<origFaceCount;i++)
  {
    GenMinFace *curFace = &mesh->Faces[i];
    if(!curFace->Select || curFace->Count <= 2 || faceGroup[i] != -1)
      continue;

    sInt *faceStackPtr = groupNext;
    *faceStackPtr++ = i;

    while(faceStackPtr != groupNext)
    {
      sInt top = *--faceStackPtr;
      faceGroup[top] = groupCount;

      GenMinFace *face = &mesh->Faces[top];
      for(sInt j=0;j<face->Count;j++)
      {
        sInt adjacent = face->Adjacent[j] >> 3;
        GenMinFace *aface = &mesh->Faces[adjacent];

        if(adjacent != -1 && faceGroup[adjacent] == -1 && aface->Select
          && face->Normal.Dot(aface->Normal) >= angThresh)
        {
          faceGroup[adjacent] = -2;
          *faceStackPtr++ = adjacent;
        }
      }
    }

    groupCount++;
  }

  // build linked lists for groups
  sInt *groupFirst = new sInt[groupCount];
  sSetMem(groupFirst,0xff,sizeof(sInt) * groupCount);
  for(sInt i=origFaceCount-1;i>=0;i--)
  {
    sInt grp = faceGroup[i];
    if(grp >= 0)
    {
      groupNext[i] = groupFirst[grp];
      groupFirst[grp] = i;
    }
  }

  // iterate over extrusion steps
  while(count--)
  {
    // perform extrusion
    ExtrudeOnce(mesh,faceGroup);

    for(sInt i=0;i<mesh->Vertices.Count;i++)
      mesh->Vertices[i].Select = 0;

    for(sInt grp=0;grp<groupCount;grp++)
    {
      // skip empty groups
      sInt first = groupFirst[grp];

      // vertex displacement: first compute group normal (if necessary)
      GenMinVector avgDir;
      if(displaceMode == 0) // average (face!) normal
      {
        avgDir.Init(0,0,0);

        for(sInt face=first;face != -1;face = groupNext[face])
          avgDir.Add(mesh->Faces[face].Normal);

        avgDir.UnitSafe();
      }
      else
        avgDir.Init(1,1,1);

      // reposition vertices
      for(sInt face=first;face != -1;face = groupNext[face])
      {
        GenMinFace *curFace = &mesh->Faces[face];
        sVERIFY(curFace->Select);

        for(sInt j=0;j<curFace->Count;j++)
        {
          GenMinVert *vert = &mesh->Vertices[curFace->Vertices[j]];
          if(vert->Select)
            continue;

          const GenMinVector *disp = &avgDir;
          if(displaceMode == 1) // individual normal
            disp = &vert->Normal;

          vert->Pos.x += disp->x * distance.x;
          vert->Pos.y += disp->y * distance.y;
          vert->Pos.z += disp->z * distance.z;
          vert->Select = 1;
        }
      }
    }
  }

  // swap stitches to end
  sInt pos = mesh->Faces.Count;
  for(sInt i=0;i<pos;i++)
    if(mesh->Faces[i].Count == 2)
      sSwap(mesh->Faces[i],mesh->Faces[--pos]);

  // cleanup
  delete[] faceGroup;
  delete[] groupFirst;
  delete[] groupNext;
  mesh->ChangeTopo();

  return mesh;
}

/****************************************************************************/
/****************************************************************************/

class GenKineticSpline : public GenSpline
{
public:
  sVector Pos;
  sVector Speed;
  sVector Gravity;
  sVector Rotation;
  GenKineticSpline();
  void Eval(sF32 time,sF32 phase,sMatrix &mat,sF32 &zoom);
};

GenKineticSpline::GenKineticSpline()
{
  Pos.Init();
  Speed.Init();
  Gravity.Init();
  Rotation.Init();
}

void GenKineticSpline::Eval(sF32 time,sF32 phase,sMatrix &mat,sF32 &zoom)
{
  if(time<0) time = 0;
  sF32 t2 = time*time;
  mat.InitEuler(Rotation.x*sPI2F*time,Rotation.y*sPI2F*time,Rotation.z*sPI2F*time);
  mat.l.x = Pos.x + Speed.x*time + Gravity.x*t2;
  mat.l.y = Pos.y + Speed.y*time + Gravity.y*t2;
  mat.l.z = Pos.z + Speed.z*time + Gravity.z*t2;
  zoom = 0;
}

/****************************************************************************/

class GenKineticSpline2 : public GenSpline
{
public:
  sVector Pos;
  sVector Speed;
  sVector Gravity;
  sVector Axis;
  sF32 Angle;
  GenKineticSpline2();
  void Eval(sF32 time,sF32 phase,sMatrix &mat,sF32 &zoom);
};

GenKineticSpline2::GenKineticSpline2()
{
  Pos.Init();
  Speed.Init();
  Gravity.Init();
  Axis.Init(0,0,1);
  Angle = 0;
}

void GenKineticSpline2::Eval(sF32 time,sF32 phase,sMatrix &mat,sF32 &zoom)
{
  if(time<0) time = 0;
  sF32 t2 = time*time;
  mat.InitRot(Axis,Angle*time*sPI2F);
  mat.l.x = Pos.x + Speed.x*time + Gravity.x*t2;
  mat.l.y = Pos.y + Speed.y*time + Gravity.y*t2;
  mat.l.z = Pos.z + Speed.z*time + Gravity.z*t2;
  zoom = 0;
}

/****************************************************************************/

static void area(const sVector &p0,const sVector &p1,const sVector &p2,sVector &c)
{
  sVector a,b;
  a.Sub3(p0,p1);
  b.Sub3(p1,p2);
  c.Cross3(a,b);
}

GenMinMesh * __stdcall MinMesh_Explode(GenMinMesh *input,GenBitmap *bitmap,
  sF323 center,sF32 extrude,sF32 speednormal,sF32 speedcenter,sF32 speedgravity,sF32 speedrandom,
  sF323 rotationspeed,sF32 todist,sF32 topower,sF32 torandom,sF32 toconst,
  sInt mode,sF323 towards,sF32 extrudeflat,sF32 tensorrand)
{
  GenMinMesh *mesh;
  GenMinFace *face;
  GenMinVert *vert,*vert0,*vert1;
  GenMinFace *fp;
  GenMinVert *vp;
  BilinearContext sampler;

  mesh = new GenMinMesh;
  extrudeflat = 1-extrudeflat;

  towards.x -= center.x;
  towards.y -= center.y;
  towards.z -= center.z;
  sF32 towardsdiv = towards.x*towards.x + towards.y*towards.y + towards.z*towards.z;
  sF32 towards2x = towards.x/towardsdiv;
  sF32 towards2y = towards.y/towardsdiv;
  sF32 towards2z = towards.z/towardsdiv;

  // count & check limit

  input->CalcNormals();

  sInt fc=0;
  sInt vc=0;
  sInt faces=0;
  sInt i;

  for(sInt i=0;i<input->Faces.Count;i++)
  {
    face = &input->Faces[i];
    sInt cnt = face->Count;
    if(cnt>=3 && face->Cluster>0)
    {
      fc += 2*(cnt-2) + cnt;
      vc += 2*cnt     + 4*cnt;
      faces++;
    }
  }

  if(faces>0xffff || faces<=0)
  {
    input->Release();
    return mesh;
  }

  // create geometry

  mesh->Vertices.SetMax(vc); 
  mesh->Vertices.Count = vc;
  vp = &mesh->Vertices[0];
  sSetMem(vp,0,vc*sizeof(*vp));
  mesh->Faces.SetMax(fc); 
  mesh->Faces.Count = fc;
  fp = &mesh->Faces[0];
  sSetMem(fp,0,fc*sizeof(*fp));

  sSetRndSeed(1);

  
  vc = 0;
  i = 0;
  for(sInt ii=0;ii<input->Faces.Count;ii++)
  {
    GenMinVector facecenter;
    face = &input->Faces[ii];
    if(face->Count<3 || face->Cluster==0) continue;

    sInt cnt = face->Count;
    facecenter.Init();
    for(sInt j=0;j<cnt;j++)
      facecenter.Add(input->Vertices[face->Vertices[j]].Pos);
    facecenter.Scale(1.0f/cnt);

    // top and bottom

    sInt va[KMM_MAXVERT];
    for(sInt k=0;k<cnt;k++)
      va[k] = face->Vertices[k];
    if(cnt==4 && (mode&2))
    {
      sVector p0,p1,p2,p3;
      p0.Init(input->Vertices[va[0]].Pos.x,input->Vertices[va[0]].Pos.y,input->Vertices[va[0]].Pos.z);
      p1.Init(input->Vertices[va[1]].Pos.x,input->Vertices[va[1]].Pos.y,input->Vertices[va[1]].Pos.z);
      p2.Init(input->Vertices[va[2]].Pos.x,input->Vertices[va[2]].Pos.y,input->Vertices[va[2]].Pos.z);
      p3.Init(input->Vertices[va[3]].Pos.x,input->Vertices[va[3]].Pos.y,input->Vertices[va[3]].Pos.z);

      sVector a0,a1,a2,a3;

      area(p0,p1,p2,a1);
      area(p1,p2,p3,a2);
      area(p2,p3,p0,a3);
      area(p3,p0,p1,a0);

      if(a0.Dot3(a2) > a1.Dot3(a3))
      {
        sInt s = va[0];
        va[0] = va[1];
        va[1] = va[2];
        va[2] = va[3];
        va[3] = s;
      }
    }
    

    for(sInt j=2;j<cnt;j++)
    {
      fp->Count = 3;
      fp->Cluster = 1;
      fp->Flags = 0;
      fp->Vertices[0] = vc+0;
      fp->Vertices[1] = vc+j-1;
      fp->Vertices[2] = vc+j;
      fp++;
    }
    for(sInt j=2;j<cnt;j++)
    {
      fp->Count = 3;
      fp->Cluster = 1;
      fp->Flags = 0;
      fp->Vertices[2] = vc+cnt+0;
      fp->Vertices[1] = vc+cnt+j-1;
      fp->Vertices[0] = vc+cnt+j;
      fp++;
    }

    for(sInt j=0;j<cnt;j++)
    {
      vert = &input->Vertices[va[j]];

      vp[j] = *vert;
      vp[j].MergeTag = ii;
      vp[j].BoneCount = 1;
      vp[j].Matrix[0] = i;
      vp[j].Weights[0] = 1;
      vp[j+cnt] = vp[j];
      vp[j+cnt].Pos.Sub(facecenter);    vp[j+cnt].Pos.Scale(extrudeflat);   vp[j+cnt].Pos.Add(facecenter);
      vp[j+cnt].Pos.AddScale(vert->Normal,-extrude);
    }
    vc += face->Count*2; 
    vp += face->Count*2;

    // around

    for(sInt j=0;j<cnt;j++)
    {
      vert0 = &input->Vertices[va[j]];
      vert1 = &input->Vertices[va[(j+1)%cnt]];
      fp->Count = 4;
      fp->Cluster = 1;
      fp->Select = 1;
      fp->Flags = 0;
      fp->Vertices[0] = vc+2;
      fp->Vertices[1] = vc+3;
      fp->Vertices[2] = vc+1;
      fp->Vertices[3] = vc+0;
      vp[0] = *vert0;
      vp[0].MergeTag = ii;
      vp[0].BoneCount = 1;
      vp[0].Matrix[0] = i;
      vp[0].Weights[0] = 1;
      vp[1] = *vert1;
      vp[1].MergeTag = ii;
      vp[1].BoneCount = 1;
      vp[1].Matrix[0] = i;
      vp[1].Weights[0] = 1;
      vp[2] = vp[0];
      vp[2].Pos.Sub(facecenter);    vp[2].Pos.Scale(extrudeflat);   vp[2].Pos.Add(facecenter);
      vp[2].Pos.AddScale(vert0->Normal,-extrude);
      vp[3] = vp[1];
      vp[3].Pos.Sub(facecenter);    vp[3].Pos.Scale(extrudeflat);   vp[3].Pos.Add(facecenter);
      vp[3].Pos.AddScale(vert1->Normal,-extrude);

      vc+=4;
      vp+=4;
      fp+=1;
    }
    i++;
  }

  // create animation

  if(bitmap)
    BilinearSetup(&sampler,bitmap->Data,bitmap->XSize,bitmap->YSize,0);

  mesh->Clusters[1].AnimType = 2;
  mesh->CreateAnimation(faces);
  i = 0;
  for(sInt ii=0;ii<input->Faces.Count;ii++)
  {
    GenMinVector facecenter,speed,f;
    GenMinMatrix *amat;

    face = &input->Faces[ii];
    if(face->Count<3 || face->Cluster==0) continue;
    facecenter.Init();
    for(sInt j=0;j<face->Count;j++)
      facecenter.Add(input->Vertices[face->Vertices[j]].Pos);
    facecenter.Scale(1.0f/face->Count);

    speed.Init();
    f.x = facecenter.x - center.x;
    f.y = facecenter.y - center.y;
    f.z = facecenter.z - center.z;
    speed.AddScale(f,speedcenter);
    speed.AddScale(face->Normal,speednormal);
    speed.x += (sFGetRnd()*2-1)*speedrandom;
    speed.y += (sFGetRnd()*2-1)*speedrandom;
    speed.z += (sFGetRnd()*2-1)*speedrandom;

    amat = &mesh->Animation->Matrices[i];
    amat->Temp.Init();
    amat->Temp.l.x = facecenter.x;
    amat->Temp.l.y = facecenter.y;
    amat->Temp.l.z = facecenter.z;
    amat->BasePose.Invert(amat->Temp);
    amat->NoAnimation = amat->Temp;
    if(bitmap)
    {
      sF32 u0,v0;
      sU64 col64;
      sF32 val;

      u0 = v0 = 0;
      for(sInt j=0;j<face->Count;j++)
      {
        u0 += input->Vertices[face->Vertices[j]].UV[0][0];
        v0 += input->Vertices[face->Vertices[j]].UV[0][1];
      }
      u0 /= face->Count;
      v0 /= face->Count;

      BilinearFilter(&sampler,&col64,sInt(u0*0x10000*bitmap->XSize),sInt(v0*0x10000*bitmap->XSize));
      if((col64&0xffff)!=0)
      {
        val = 1-((col64&0xffff)/32767.0f);
        amat->Offset = -todist*sFPow(val,topower);
      }
      else
      {
        amat->Offset = -9999;
      }
    }
    else
    {
      if(mode&1)
      {
        sF32 dist = (facecenter.x-center.x)*towards2x
                  + (facecenter.y-center.y)*towards2y
                  + (facecenter.z-center.z)*towards2z;

        f.x = facecenter.x-center.x - dist*towards.x;
        f.y = facecenter.y-center.y - dist*towards.y;
        f.z = facecenter.z-center.z - dist*towards.z;

        sF32 dist2 = sFSqrt(f.x*f.x+f.y*f.y+f.z*f.z);

        dist2 -= topower;
        if(dist2 < 0)
        {
          if(dist2<-topower)
            dist2 = 0;
          else 
            dist2 = topower+dist2;
          dist -= sFSqrt(topower*topower-dist2*dist2)/sFSqrt(towardsdiv);
          dist2 = 0;
        }
        amat->Offset = -todist*dist2-dist;
      }
      else
      {
        amat->Offset = -todist*sFPow(f.x*f.x+f.y*f.y+f.z*f.z,topower);
      }
    }
    amat->Offset += - toconst - sFGetRnd()*torandom;
    if(!(mode & 4))  // euler
    {
      GenKineticSpline *spline;
      amat->Spline = spline = new GenKineticSpline;
      spline->Pos.Init(facecenter.x,facecenter.y,facecenter.z);
      spline->Speed.Init(speed.x,speed.y,speed.z);
      spline->Gravity.Init(0,speedgravity,0);
      spline->Rotation.Init((sFGetRnd()*2-1)*rotationspeed.x,(sFGetRnd()*2-1)*rotationspeed.y,(sFGetRnd()*2-1)*rotationspeed.z);
    }
    else      // vector & axis, for tensor
    {
      GenKineticSpline2 *spline;
      amat->Spline = spline = new GenKineticSpline2;
      spline->Pos.Init(facecenter.x,facecenter.y,facecenter.z);
      spline->Speed.Init(speed.x,speed.y,speed.z);
      spline->Gravity.Init(0,speedgravity,0);
      spline->Axis.Init((sFGetRnd()*2-1),(sFGetRnd()*2-1),(sFGetRnd()*2-1));
      spline->Axis.Unit3();
      spline->Angle = (sFGetRnd()*2-1)*tensorrand;
    }
    i++;
  }

  mesh->CompletelyRigid = sTRUE;

  // done

  input->Release();
  mesh->ChangeTopo();
  mesh->CalcNormals();

  if(bitmap)
    bitmap->Release();

  return mesh;
}

/****************************************************************************/
/***                                                                      ***/
/***   Mesh Compression                                                   ***/
/***                                                                      ***/
/****************************************************************************/

static sInt GetValence(GenMinMesh *mesh,sInt edgeTag)
{
  sInt e = edgeTag;
  sInt count = 0;

  do
  {
    count++;
    e = mesh->NextVertEdge(e);
  }
  while(e != edgeTag);

  return count;
}

struct MeshElement
{
  sInt Degree;
  sInt Edge;
  MeshElement **Adjacent;

  sInt Find(MeshElement *ref,sInt adjust) const;
};

sInt MeshElement::Find(MeshElement *ref,sInt adjust) const
{
  for(sInt i=0;i<Degree;i++)
    if(Adjacent[i] == ref)
      return (i + adjust + Degree) % Degree;

  sVERIFYFALSE;
  return -1;
}

class MeshCoder
{
  GenMinMesh *Mesh;

  MeshElement *Elems[2];  // 0=faces 1=vertices
  sInt ElemCount[2];      // 0=faces 1=vertices
  MeshElement **Links,**LinkPtr,**LinkEnd;
  
  MeshElement **FaceMap;
  MeshElement **VertMap;
  sU8 *Data;
  sInt FaceCount;
  sInt VertCount;
  sInt *VertMerge;
  sInt *VertOrder;

  sArray<sInt> Active;

  // encode and decode
  MeshElement *AddElement(sInt type,sInt deg);
  void AddFaceToVertex(MeshElement *f,sInt i,MeshElement *v,sInt j);
  sInt ForceFV(MeshElement *f,sInt j,sInt dir);

  // encode only
  void ActivateVE(MeshElement *f,sInt i);

  // decode only
  MeshElement *DecodeFace();
  void ActivateVD(MeshElement *f,sInt i);

  // debug only
  void AssertSingleFV(MeshElement *f,MeshElement *v);
  void AssertFV(MeshElement *f);
  void AssertLinkedEdge(MeshElement *v);

public:
  // regularizing a mesh
  GenMinMesh *CloseBoundaries(GenMinMesh *mesh);

  // main coding api
  sInt *Encode(GenMinMesh *mesh,sU8 *&p,sInt &outVertCount);
  GenMinMesh *Decode(sU8 *&p);
};

static sInt FindEdgeByVert(GenMinTempEdge *list,sInt n,sInt startVert)
{
  // binary search
  sInt l,r,x;

  l = 0;
  r = n;
  
  while(l < r)
  {
    x = (l + r) / 2;

    if(list[x].v0 == startVert)
      return x;
    else if(list[x].v0 < startVert) // continue in right half
      l = x + 1;
    else // continue in left half
      r = x;
  }

  return -1;
}

GenMinMesh *MeshCoder::CloseBoundaries(GenMinMesh *inMesh)
{
  // first, just make a copy of the mesh
  GenMinMesh *mesh = new GenMinMesh;
  mesh->Copy(inMesh);

  // one-to-one remap
  sInt *remap = new sInt[mesh->Vertices.Count];
  for(sInt i=0;i<mesh->Vertices.Count;i++)
    remap[i] = i;

  // build adjacency, step 1
  mesh->CalcAdjacencyCore(remap);
  //mesh->CalcAdjacency();
  delete[] remap;

  // make list of boundary edges
  sArray<GenMinTempEdge> boundaryEdges;
  boundaryEdges.Init();

  for(sInt i=0;i<mesh->Faces.Count;i++)
  {
    GenMinFace *curFace = &mesh->Faces[i];

    for(sInt j=0;j<curFace->Count;j++)
    {
      if(curFace->Adjacent[j] == -1) // boundary
      {
        GenMinTempEdge *te = boundaryEdges.Add();
        te->v0 = curFace->Vertices[(j+1) % curFace->Count];
        te->v1 = curFace->Vertices[j];
      }
    }
  }

  // sort boundary edges by first vertex
  if(boundaryEdges.Count)
    HeapSortEdges(&boundaryEdges[0],boundaryEdges.Count);

  // fill boundaries
  sInt boundaryFaces = 0;

  while(boundaryEdges.Count)
  {
    // insert a boundary helper vertex
    sInt boundaryVert = mesh->Vertices.Count;
    GenMinVert *tempVert = mesh->Vertices.Add();
    tempVert->Pos.Init(0,0,0);

    // pick the first boundary edge available and follow the loop
    sInt vertex = boundaryEdges[0].v1;
    sInt edge;

    do
    {
      // find next edge in this loop
      edge = FindEdgeByVert(&boundaryEdges[0],boundaryEdges.Count,vertex);
      sVERIFY(edge != -1); // we should ALWAYS be able to find one.
      sInt nextVertex = boundaryEdges[edge].v1;

      // deleted this edge from the list of candidates
      boundaryEdges.Count--;
      for(sInt i=edge;i<boundaryEdges.Count;i++)
        boundaryEdges[i] = boundaryEdges[i+1];

      // insert a "deleted" face to close the mesh
      GenMinFace *face = mesh->Faces.Add();
      face->Count = 3;
      face->Vertices[0] = boundaryVert;
      face->Vertices[1] = vertex;
      face->Vertices[2] = nextVertex;
      boundaryFaces++;

      // continue following
      vertex = nextVertex;
    }
    while(edge != 0);
  }

  boundaryEdges.Exit();

  sDPrintF("%d faces inserted to close boundary.\n",boundaryFaces);

  // everything should be closed now. re-build adjacency and check.
  remap = new sInt[mesh->Vertices.Count];
  for(sInt i=0;i<mesh->Vertices.Count;i++)
    remap[i] = i;

  sBool closed = mesh->CalcAdjacencyCore(remap);
  //sBool closed = mesh->CalcAdjacency();
  delete[] remap;

  // assert everything was ok and return the new mesh
  sVERIFY(closed);

  return mesh;
}

MeshElement *MeshCoder::AddElement(sInt type,sInt deg)
{
  MeshElement *elem = &Elems[type][ElemCount[type]++];
  elem->Degree = deg;
  elem->Adjacent = LinkPtr;
  memset(LinkPtr,0,deg * sizeof(MeshElement *));

  LinkPtr += deg;
  sVERIFY(LinkPtr <= LinkEnd);

  sVERIFY(ElemCount[0] <= FaceCount);
  sVERIFY(ElemCount[1] <= VertCount);

  return elem;
}

void MeshCoder::AddFaceToVertex(MeshElement *f,sInt i,MeshElement *v,sInt j)
{
  v->Adjacent[j] = f;

  for(sInt dir=-1;dir<=1;dir+=2)
  {
    MeshElement *fp = v->Adjacent[(j+dir+v->Degree)%v->Degree];
    sInt in = (i-dir+f->Degree)%f->Degree;
		if(fp && !f->Adjacent[in])
			f->Adjacent[in] = fp->Adjacent[fp->Find(v,dir)];
  }

  AssertLinkedEdge(v);
}

sInt MeshCoder::ForceFV(MeshElement *f,sInt j,sInt dir)
{
  MeshElement *v = f->Adjacent[0];
  sInt i = 0;
  
  while(1)
  {
    i = (i + dir + f->Degree) % f->Degree;
    MeshElement *vt = v->Adjacent[(j - dir + v->Degree) % v->Degree];
    v = f->Adjacent[i];

    if(!i || !vt || !v)
      break;

    j = v->Find(vt,-dir);
    AddFaceToVertex(f,i,v,j);
  }

  return i;
}

void MeshCoder::ActivateVE(MeshElement *f,sInt i)
{
  GenMinFace *face = &Mesh->Faces[f->Edge >> 3];
  sInt vertSlot = ((f->Edge & 7) + i) % face->Count;
  sInt edgeTag = (f->Edge & ~7) + vertSlot;
  sInt vertex = face->Vertices[vertSlot];
  MeshElement *v;

  // (try to) find vertex in active vertex list
  sInt j;
  for(j=0;j<Active.Count;j++)
    if(Active[j] == vertex)
      break;

  if(j == Active.Count) // new vertex
  {
    sVERIFY(VertMap[vertex] == 0);

    sInt val = GetValence(Mesh,edgeTag);
    v = AddElement(1,val);

    *Data++ = val;
    VertOrder[ElemCount[1]-1] = vertex;

    f->Adjacent[i] = v;
    v->Adjacent[0] = f;

    v->Edge = edgeTag;
    *Active.Add() = vertex;
    VertMap[vertex] = v;
  }
  else // split
  {
    v = VertMap[vertex];
    sVERIFY(j < 65536);

    *Data++ = 0;
    *(sU16 *) Data = j; Data += 2;

    j=0;
    sInt e,ee;
    
    e = ee = v->Edge;
    while((e & ~7) != (f->Edge & ~7))
    {
      j++;
      e = Mesh->NextVertEdge(e);
    }

    *Data++ = j;
    f->Adjacent[i] = v;
    AddFaceToVertex(f,i,v,j);
  }

  AssertSingleFV(f,v);
}

MeshElement *MeshCoder::DecodeFace()
{
  sInt deg = *Data++;
  MeshElement *f = AddElement(0,deg);

  return f;
}

void MeshCoder::ActivateVD(MeshElement *f,sInt i)
{
  MeshElement *v;
  sInt val = *Data++;
  sInt j;

  if(val) // new vertex
  {
    v = AddElement(1,val);
    *Active.Add() = v - Elems[1];

    j = 0;

    f->Adjacent[i] = v;
    v->Adjacent[0] = f;
  }
  else // split
  {
    v = &Elems[1][Active[*(sU16 *) Data]]; Data += 2;
    j = *Data++;

    f->Adjacent[i] = v;
    AddFaceToVertex(f,i,v,j);
  }

  AssertSingleFV(f,v);
}

void MeshCoder::AssertSingleFV(MeshElement *f,MeshElement *v)
{
  sInt i,j;

  for(i=0;i<f->Degree;i++)
  {
    if(v == f->Adjacent[i])
    {
      for(j=0;j<v->Degree;j++)
        if(v->Adjacent[j] == f)
          break;

      sVERIFY(j != v->Degree);
      break;
    }
  }

  sVERIFY(i != f->Degree);
}

void MeshCoder::AssertFV(MeshElement *f)
{
  sInt i,j;

  // assert FV consistency
  for(i=0;i<f->Degree;i++)
  {
    MeshElement *v = f->Adjacent[i];
    if(!v)
      continue;

    for(j=0;j<v->Degree;j++)
      if(v->Adjacent[j] == f)
        break;

    sVERIFY(j != v->Degree);
  }

  // then assert that we actually represent the right topology
  sInt e = f->Edge;

  for(i=0;i<f->Degree;i++)
  {
    MeshElement *realVert = VertMap[Mesh->Faces[e >> 3].Vertices[e & 7]];
    sVERIFY(realVert == f->Adjacent[i]);

    e = Mesh->NextFaceEdge(e);
  }
}

void MeshCoder::AssertLinkedEdge(MeshElement *v)
{
  sInt j,lj;

  // assert linked edge consistency
  lj = v->Degree - 1;
  for(j=0;j<v->Degree;j++)
  {
    MeshElement *f1 = v->Adjacent[j];
    MeshElement *f2 = v->Adjacent[lj];

    if(f1 != 0 && f2 != 0)
    {
      sBool consistent = sFALSE;

      for(sInt i1=0;i1<f1->Degree;i1++)
        for(sInt i2=0;i2<f2->Degree;i2++)
          if(f1->Adjacent[i1] == f2->Adjacent[i2])
            consistent = sTRUE;

      sVERIFY(consistent);
    }

    lj = j;
  }

  // then assert that we actually represent the right topology
  sInt e = v->Edge;

  for(sInt i=0;i<v->Degree;i++)
  {
    MeshElement *realFace = FaceMap[e >> 3];
    sVERIFY(!v->Adjacent[i] || realFace == v->Adjacent[i]);

    e = Mesh->NextVertEdge(e);
  }
}

sInt *MeshCoder::Encode(GenMinMesh *mesh,sU8 *&p,sInt &outVertCount)
{
  // currently assumes a closed mesh
  // additional simplifications apply:
  // - no deleted faces
  Mesh = mesh;

  // count real number of faces and vertices
  FaceCount = 0;
  for(sInt i=0;i<mesh->Faces.Count;i++)
    FaceCount += mesh->Faces[i].Count >= 3;

  VertCount = 0;
  VertMerge = mesh->CalcMergeVerts();
  for(sInt i=0;i<mesh->Vertices.Count;i++)
    VertCount += VertMerge[i] == i;

  // remember everything i've said about counting the real number of
  // vertices and stuff? just ignore it.
  VertCount = mesh->Vertices.Count;

  // create data structures
  FaceMap = new MeshElement *[mesh->Faces.Count];
  VertMap = new MeshElement *[mesh->Vertices.Count];
  memset(FaceMap,0,mesh->Faces.Count * sizeof(sInt));
  memset(VertMap,0,mesh->Vertices.Count * sizeof(sInt));

  Elems[0] = new MeshElement[FaceCount];
  Elems[1] = new MeshElement[VertCount];
  ElemCount[0] = 0;
  ElemCount[1] = 0;

  VertOrder = new sInt[VertCount];

  // count number of edges, reserve space for links
  sInt edgeCount = 0;
  for(sInt i=0;i<mesh->Faces.Count;i++)
    if(mesh->Faces[i].Count >= 3)
      edgeCount += mesh->Faces[i].Count;

  Links = new MeshElement *[edgeCount * 2];
  LinkPtr = Links;
  LinkEnd = Links + (edgeCount * 2);
  Active.Init();

  Data = p;

  *(sU32 *) Data = VertCount; Data += 4;
  *(sU32 *) Data = FaceCount; Data += 4;
  *(sU32 *) Data = edgeCount * 2; Data += 4;

  // encode connected components in turn
  for(sInt seedFace=0;seedFace<mesh->Faces.Count;seedFace++)
  {
    // face already encoded? skip.
    if(FaceMap[seedFace] || mesh->Faces[seedFace].Count == 2)
      continue;

    // encode seed face
    sInt k = mesh->Faces[seedFace].Count;
    *Data++ = k;

    MeshElement *f = AddElement(0,k);
    f->Edge = seedFace << 3;
    FaceMap[seedFace] = f;

    for(sInt i=0;i<k;i++)
      ActivateVE(f,i);

    while(Active.Count)
    {
      // pick vertex to complete
      sInt bestDegree = 256;
      sInt bestI = -1;
      MeshElement *v = 0;
      
      for(sInt i=0;i<Active.Count;i++)
      {
        MeshElement *testV = VertMap[Active[i]];
        sVERIFY(testV != 0);

        sInt k = 0;
        for(sInt j=0;j<testV->Degree;j++)
          k += !testV->Adjacent[j];

        if(k < bestDegree)
        {
          bestI = i;
          v = testV;
          bestDegree = k;
        }
      }

      // try to complete this vertex
      sInt edge = v->Edge;

      for(sInt j=0;j<v->Degree;j++)
      {
        if(!v->Adjacent[j])
        {
          // activate this face
          sInt face = edge >> 3;
          sVERIFY(FaceMap[face] == 0);
          sInt d = Mesh->Faces[face].Count;
          *Data++ = d;

          MeshElement *f = AddElement(0,d);
          FaceMap[face] = f;
          f->Edge = edge;

          f->Adjacent[0] = v;
          AddFaceToVertex(f,0,v,j);

          // complete this face
          sInt i = ForceFV(f,j,1);
          sInt iend = ForceFV(f,j,-1);

          if(i)
            while(i <= iend)
              ActivateVE(f,i++);

          // verify that this face is fully and correctly connected now
          for(i=0;i<d;i++)
            sVERIFY(f->Adjacent[i] != 0);

          AssertFV(f);
        }
        
        edge = mesh->NextVertEdge(edge);
      }

      sVERIFY(edge == v->Edge);

      // remove this vertex from the queue
      Active[bestI] = Active[--Active.Count];
    }
  }

  sVERIFY(ElemCount[0] == FaceCount);
  sVERIFY(ElemCount[1] == VertCount);
  
  outVertCount = VertCount;
  p = Data;

  Active.Exit();
  delete[] Links;
  delete[] Elems[0];
  delete[] Elems[1];
  delete[] FaceMap;
  delete[] VertMap;
  delete[] VertMerge;

  return VertOrder;
}

GenMinMesh *MeshCoder::Decode(sU8 *&p)
{
  Mesh = new GenMinMesh;
  Data = p;

  sU32 vertCount,faceCount,linkCount;
  vertCount = *(sU32 *) Data; Data += 4;
  faceCount = *(sU32 *) Data; Data += 4;
  linkCount = *(sU32 *) Data; Data += 4;

  Elems[0] = new MeshElement[faceCount];
  Elems[1] = new MeshElement[vertCount];
  ElemCount[0] = 0;
  ElemCount[1] = 0;

  Links = new MeshElement *[linkCount];
  LinkPtr = Links;
  LinkEnd = Links + linkCount;
  Active.Init();

  // while not all faces are coded
  while(ElemCount[0] < sInt(faceCount))
  {
    // get a seed face and activate its vertices
    MeshElement *f = DecodeFace();

    for(sInt i=0;i<f->Degree;i++)
      ActivateVD(f,i);

    // complete this connected component
    do
    {
      // pick vertex to complete
      sInt bestDegree = 256;
      sInt bestI;

      for(sInt i=0;i<Active.Count;i++)
      {
        MeshElement *v = &Elems[1][Active[i]];
        sInt deg = 0;

        for(sInt k=0;k<v->Degree;k++)
          deg += !v->Adjacent[k];

        if(deg < bestDegree)
        {
          bestDegree = deg;
          bestI = i;
        }
      }

      // complete this vertex
      MeshElement *v = &Elems[1][Active[bestI]];

      for(sInt j=0;j<v->Degree;j++)
      {
        if(!v->Adjacent[j])
        {
          f = DecodeFace();
          f->Adjacent[0] = v;
          AddFaceToVertex(f,0,v,j);

          sInt i = ForceFV(f,j,1);
          sInt iend = ForceFV(f,j,-1);

          if(i)
            while(i <= iend)
              ActivateVD(f,i++);
        }
      }

      Active[bestI] = Active[--Active.Count];
    }
    while(Active.Count);
  }

  // now convert to mesh
  Mesh->Vertices.Resize(vertCount);
  Mesh->Faces.Resize(faceCount);

  for(sInt i=0;i<ElemCount[1];i++)
    Elems[1][i].Edge = i;

  for(sInt i=0;i<ElemCount[0];i++)
  {
    MeshElement *f = &Elems[0][i];
    GenMinFace *face = &Mesh->Faces[i];

    face->Count = f->Degree;
    for(sInt j=0;j<f->Degree;j++)
      face->Vertices[j] = f->Adjacent[j]->Edge;

    face->Cluster = 1;
  }

  Mesh->ChangeTopo();

  // cleanup.
  Active.Exit();
  delete[] Links;
  delete[] Elems[0];
  delete[] Elems[1];

  p = Data;
  return Mesh;
}

GenMinMesh * __stdcall MinMesh_Compress(GenMinMesh *inMesh)
{
  if(CheckMinMesh(inMesh))
    return 0;

  MeshCoder coder;
  static sU8 buffer[256*1024];
  sU8 *bufPtr = buffer;

  // ---- regularize
  sDPrintF("-- regularize\n");
  GenMinMesh *mesh = coder.CloseBoundaries(inMesh);
  inMesh->Release();

  // ---- encode
  sDPrintF("-- encode\n");

  // encode topology
  sInt vertCount;
  sInt *vertOrder = coder.Encode(mesh,bufPtr,vertCount);
  sSystem->SaveFile("topo.dat",buffer,bufPtr - buffer);
  sDPrintF("topology coded size: %d bytes\n",bufPtr - buffer);

  // encode vertex positions
  for(sInt i=0;i<vertCount;i++)
  {
    GenMinVert *vert = &mesh->Vertices[vertOrder[i]];

    *((sF32 *) bufPtr) = vert->Pos.x; bufPtr += 4;
    *((sF32 *) bufPtr) = vert->Pos.y; bufPtr += 4;
    *((sF32 *) bufPtr) = vert->Pos.z; bufPtr += 4;
  }

  delete[] vertOrder;

  // coding stats
  mesh->Release();
  sDPrintF("coded size: %d bytes\n",bufPtr - buffer);

  // ---- decode

  sDPrintF("-- decode\n");

  // decode topology
  bufPtr = buffer;
  mesh = coder.Decode(bufPtr);

  // decode vertex positions
  for(sInt i=0;i<mesh->Vertices.Count;i++)
  {
    GenMinVert *vert = &mesh->Vertices[i];

    vert->Pos.x = *((sF32 *) bufPtr); bufPtr += 4;
    vert->Pos.y = *((sF32 *) bufPtr); bufPtr += 4;
    vert->Pos.z = *((sF32 *) bufPtr); bufPtr += 4;
  }

  // stats
  sDPrintF("decoded, read %d bytes\n",bufPtr - buffer);

  return mesh;
}

/****************************************************************************/

#pragma comment(lib,"glu32.lib")

struct GLUtesselator;

extern "C"
{
  // Win32
  typedef void *HDC;
  typedef void *HFONT;

  struct FIXED
  {
    union
    {
      struct
      {
        sU16 fract;
        sS16 value;
      };
      sS32 full;
    };
  };

  struct POINTFX
  {
    FIXED x;
    FIXED y;

    GenMinVector toVector(sInt xShift) const
    {
      GenMinVector vect;

      vect.Init((xShift + x.full / 65536.0f) / 128.0f,y.full / (65536.0f * 128.0f),0.0f);
      return vect;
    }
  };

  struct MAT2
  {
    FIXED eM11,eM12;
    FIXED eM21,eM22;
  };

  struct GLYPHMETRICS
  {
    sU32 gmBlackBoxX,gmBlackBoxY;
    sInt origin[2];
    sS16 gmCellIncX,gmCellIncY;
  };

  struct TTPOLYGONHEADER
  {
    sU32 cb;
    sU32 dwType;
    POINTFX pfxStart;
  };

  struct TTPOLYCURVE
  {
    sU16 wType;
    sU16 cpfx;
    POINTFX apfx[1];
  };

  HDC __stdcall CreateCompatibleDC(HDC hDC);
  HFONT __stdcall CreateFontA(sInt height,sInt width,sInt escape,sInt orient,
    sInt weight,sInt italic,sInt underline,sInt strikeOut,sU32 charSet,
    sU32 outPrecision,sU32 clipPrecision,sU32 quality,sU32 pitchAndFamily,
    const sChar *face);
  HFONT __stdcall SelectObject(HDC hDC,HFONT hFont);
  sU32 __stdcall GetGlyphOutlineA(HDC hDC,sU32 nChar,sU32 fuFormat,
    GLYPHMETRICS *lpgm,sU32 cjBuffer,void *buffer,const MAT2 *lpmat2);
  void __stdcall DeleteObject(HFONT hFont);
  void __stdcall DeleteDC(HDC hDC);

  // GLU
  GLUtesselator * __stdcall gluNewTess();
  void __stdcall gluDeleteTess(GLUtesselator *tess);

  void __stdcall gluTessBeginPolygon(GLUtesselator *tess,void *polygon_data);
  void __stdcall gluTessBeginContour(GLUtesselator *tess);
  void __stdcall gluTessVertex(GLUtesselator *tess,sF64 coords[3],void *data);
  void __stdcall gluTessEndContour(GLUtesselator *tess);
  void __stdcall gluTessEndPolygon(GLUtesselator *tess);
  void __stdcall gluTessNormal(GLUtesselator *tess,sF64 x,sF64 y,sF64 z);
  void __stdcall gluTessCallback(GLUtesselator *tess,sInt which,void (__stdcall *fn)());

  typedef void (__stdcall *gluTessCB)(void);

  #define GLU_TESS_BEGIN          100100
  #define GLU_TESS_VERTEX         100101
  #define GLU_TESS_END            100102
  #define GLU_TESS_ERROR          100103
  #define GLU_TESS_EDGE_FLAG      100104
  #define GLU_TESS_COMBINE        100105
  #define GLU_TESS_BEGIN_DATA     100106
  #define GLU_TESS_VERTEX_DATA    100107
  #define GLU_TESS_END_DATA       100108
  #define GLU_TESS_ERROR_DATA     100109
  #define GLU_TESS_EDGE_FLAG_DATA 100110
  #define GLU_TESS_COMBINE_DATA   100111
}

static void font3DAddPoint(GLUtesselator *tess,GenMinMesh *mesh,const GenMinVector &pt)
{
  GenMinVert *vert = mesh->Vertices.Add();
  sSetMem(vert,0,sizeof(GenMinVert));
  vert->Pos = pt;
  vert->Normal.z = -1.0f;

  double coords[3];
  coords[0] = vert->Pos.x;
  coords[1] = vert->Pos.y;
  coords[2] = vert->Pos.z;
  gluTessVertex(tess,coords,(void *) (mesh->Vertices.Count - 1));
}

static void font3DAddCurve(GLUtesselator *tess,GenMinMesh *mesh,const GenMinVector &p1,const GenMinVector &p2,sF32 toleranceSq,sInt depth=0)
{
  const GenMinVector &p0 = mesh->Vertices[mesh->Vertices.Count-1].Pos;
  GenMinVector d;

  d.Lin3(p0,p2,0.5f);
  d.Sub(p1);

  if(depth >= 12 || d.Dot(d) <= toleranceSq)
    font3DAddPoint(tess,mesh,p2);
  else
  {
    GenMinVector l,r,m;

    l.Lin3(p0,p1,0.5f);
    r.Lin3(p1,p2,0.5f);
    m.Lin3(l,r,0.5f);

    font3DAddCurve(tess,mesh,l,m,toleranceSq,depth+1);
    font3DAddCurve(tess,mesh,r,p2,toleranceSq,depth+1);
  }
}

static void __stdcall font3DBeginCB(sInt type,GenMinMesh *mesh)
{
  if(!mesh->Faces.Count || mesh->Faces[mesh->Faces.Count-1].Count)
  {
    GenMinFace *face = mesh->Faces.Add();
    face->Select = 0;
    face->Count = 0;
    face->Cluster = 1;
    face->Temp = 0;
    face->Flags = 0;
  }
}

static void __stdcall font3DVertexCB(void *index,GenMinMesh *mesh)
{
  sInt ind = reinterpret_cast<int>(index);
  GenMinFace *face = &mesh->Faces[mesh->Faces.Count-1];
  
  face->Vertices[face->Count++] = ind;
  if(face->Count == 3)
    font3DBeginCB(0,mesh);
}

static void __stdcall font3DCombineCB(sF64 coords[3],void *d[4],sF32 w[4],sInt *out,GenMinMesh *mesh)
{
  GenMinVert *vert = mesh->Vertices.Add();
  sSetMem(vert,0,sizeof(GenMinVert));

  vert->Pos.x = coords[0];
  vert->Pos.y = coords[1];
  vert->Pos.z = coords[2];
  *out = mesh->Vertices.Count - 1;
}

static void __stdcall font3DEdgeFlagCB(sBool flag,GenMinMesh *mesh)
{
}

GenMinMesh * __stdcall MinMesh_Font3D(sF32 height,sF32 extrude,sF32 maxErr,sChar *text,sChar *font)
{
  height = sMax(height,8/128.0f);
  GenMinMesh *mesh = new GenMinMesh;

  HDC hDC = CreateCompatibleDC(0);
  HFONT hFont = CreateFontA(height*128,0,0,0,500,0,0,0,0,0,0,0,0,font);
  HFONT hOldFont = SelectObject(hDC,hFont);

  MAT2 mat;
  sSetMem(&mat,0,sizeof(mat));
  mat.eM11.value = 1;
  mat.eM22.value = 1;

  static const sInt bufSize = 256*1024;
  sU8 *buffer = new sU8[bufSize];
  sInt xPos = 0;

  GLUtesselator *tess = gluNewTess();

  gluTessNormal(tess,0.0,0.0,-1.0);
  gluTessCallback(tess,GLU_TESS_BEGIN_DATA,(gluTessCB) font3DBeginCB);
  gluTessCallback(tess,GLU_TESS_VERTEX_DATA,(gluTessCB) font3DVertexCB);
  gluTessCallback(tess,GLU_TESS_COMBINE_DATA,(gluTessCB) font3DCombineCB);
  gluTessCallback(tess,GLU_TESS_EDGE_FLAG_DATA,(gluTessCB) font3DEdgeFlagCB);

  sF32 tolerance = height * 0.1f * maxErr;
  tolerance *= tolerance;

  for(sInt chr=0;text[chr];chr++)
  {
    GLYPHMETRICS gm;
    sU32 size = GetGlyphOutlineA(hDC,text[chr],0x102,&gm,bufSize,buffer,&mat);
    if(!size)
    {
      xPos += gm.gmCellIncX;
      continue;
    }

    gluTessBeginPolygon(tess,mesh);
    sU8 *ptr = buffer, *end = buffer + size;
    while(ptr < end)
    {
      TTPOLYGONHEADER *hdr = (TTPOLYGONHEADER *) ptr;
      sU8 *polyEnd = ptr + hdr->cb;

      gluTessBeginContour(tess);
      font3DAddPoint(tess,mesh,hdr->pfxStart.toVector(xPos));
      ptr += sizeof(TTPOLYGONHEADER);

      while(ptr < polyEnd)
      {
        TTPOLYCURVE *tpc = (TTPOLYCURVE *) ptr;

        switch(tpc->wType)
        {
        case 1: // line
          for(sInt i=0;i<tpc->cpfx;i++)
            font3DAddPoint(tess,mesh,tpc->apfx[i].toVector(xPos));
          break;

        case 2: // qspline
          for(sInt i=0;i<tpc->cpfx-1;i++)
          {
            GenMinVector b = tpc->apfx[i].toVector(xPos);
            GenMinVector c = tpc->apfx[i+1].toVector(xPos);

            if(i < tpc->cpfx - 2)
              c.Lin3(b,c,0.5f);

            font3DAddCurve(tess,mesh,b,c,tolerance);
          }
        }

        ptr += sizeof(TTPOLYCURVE) + (tpc->cpfx - 1) * sizeof(POINTFX);
      }

      gluTessEndContour(tess);
    }

    gluTessEndPolygon(tess);

    xPos += gm.gmCellIncX;
  }

  // last face generated is empty.
  if(mesh->Faces.Count)
    mesh->Faces.Count--;

  // stop tesselation stuff
  gluDeleteTess(tess);
  delete[] buffer;

  SelectObject(hDC,hOldFont);
  DeleteObject(hFont);
  DeleteDC(hDC);

  // calc adjacency and try to clean up triangulation by flipping degenerate tris
  mesh->CalcAdjacency();
  for(sInt i=0;i<mesh->Faces.Count;i++)
  {
    GenMinFace *face = &mesh->Faces[i];

    const GenMinVector &v0 = mesh->Vertices[face->Vertices[0]].Pos;
    const GenMinVector &v1 = mesh->Vertices[face->Vertices[1]].Pos;
    const GenMinVector &v2 = mesh->Vertices[face->Vertices[2]].Pos;

    GenMinVector d1,d2,n;
    d1.Sub(v1,v0);
    d2.Sub(v2,v0);
    n.Cross(d2,d1);

    if(n.z < 1e-6f)
    {
      // try to flip it
      for(sInt j=0;j<3;j++)
      {
        sInt e0 = (i << 3) + j;

        if(mesh->OppositeEdge(e0) != -1)
        {
          mesh->EdgeFlip(e0);
          break;
        }
      }
    }
  }

  // extrude if necessary
  if(extrude)
  {
    // create copy of vertices moved back a bit.
    sInt oldVC = mesh->Vertices.Count;
    mesh->Vertices.Resize(oldVC*4);
    sCopyMem(&mesh->Vertices[oldVC],&mesh->Vertices[0],oldVC * sizeof(GenMinVert));
    for(sInt i=oldVC;i<oldVC*2;i++)
      mesh->Vertices[i].Pos.z = extrude;
    
    // copies of vertices for sharp edges
    sCopyMem(&mesh->Vertices[oldVC*2],&mesh->Vertices[0],oldVC * 2 * sizeof(GenMinVert));

    // create copy of faces and invert them
    sInt oldFC = mesh->Faces.Count;
    mesh->Faces.Resize(oldFC*2);
    sCopyMem(&mesh->Faces[oldFC],&mesh->Faces[0],oldFC * sizeof(GenMinFace));
    for(sInt i=oldFC;i<oldFC*2;i++)
    {
      GenMinFace *face = &mesh->Faces[i];

      sSwap(face->Vertices[0],face->Vertices[2]);
      for(sInt j=0;j<3;j++)
        face->Vertices[j] += oldVC;
    }

    // create extrusion faces
    for(sInt i=0;i<oldFC;i++)
    {
      for(sInt j=0;j<3;j++)
      {
        if(mesh->Faces[i].Adjacent[j] == -1)
        {
          sInt e0 = mesh->Faces[i].Vertices[j];
          sInt e1 = mesh->Faces[i].Vertices[(j+1)%3];

          GenMinFace *ef = mesh->Faces.Add();
          ef->Select = 0;
          ef->Count = 4;
          ef->Cluster = 1;
          ef->Temp = 0;
          ef->Vertices[0] = e1 + oldVC*2;
          ef->Vertices[1] = e0 + oldVC*2;
          ef->Vertices[2] = e0 + oldVC*3;
          ef->Vertices[3] = e1 + oldVC*3;
        }
      }
    }

    // this code is tested and works, but it should only be used when a
    // flag is set, so it's commented out for now.
    /*
    // recalc adjacency and go through edges again, doubling vertices
    // on sharp edges
    mesh->CalcAdjacency();
    mesh->ChangeTopo();
    mesh->CalcNormals();

    for(sInt i=0;i<mesh->Faces.Count;i++)
    {
      GenMinFace *face = &mesh->Faces[i];

      for(sInt j=0;j<face->Count;j++)
      {
        // only go through edges once
        sInt adj = face->Adjacent[j];
        if((i<<3) >= adj)
          continue;

        const GenMinFace *otherFace = &mesh->Faces[adj>>3];
        if(face->Normal.Dot(otherFace->Normal) <= 0.5f) // >=60° angle?
        {
          sInt vi[2];
          sInt e0v[2],e1v[2];

          vi[0] = j;
          vi[1] = (j+1 == face->Count) ? 0 : j+1;
          e0v[0] = face->Vertices[vi[0]];
          e1v[0] = mesh->GetVertexId(mesh->NextFaceEdge(adj));
          e0v[1] = face->Vertices[vi[1]];
          e1v[1] = mesh->GetVertexId(adj);

          for(sInt k=0;k<2;k++)
          {
            // only need to do something if the vertex is shared
            if(e0v[k] != e1v[k])
              continue;

            // clone it!
            sInt vInd = mesh->Vertices.Count;
            mesh->Vertices.Add();
            mesh->Vertices[vInd] = mesh->Vertices[e0v[k]];
            face->Vertices[vi[k]] = vInd;
          }
        }
      }
    }*/
  }

  sMatrix mtx;
  mtx.Init();
  mtx.k.x = 1.0f;
  mesh->Transform(0,mtx,0,1);

  mesh->ChangeTopo();

  return mesh;
}

/****************************************************************************/
/****************************************************************************/

GenMinMesh * __stdcall MinMesh_Pipe(GenSpline *spline_,GenMinMesh *mesh0,GenMinMesh *mesh1,GenMinMesh *mesh2,sInt flags,sF32 texzoom,sF32 ringdist,sF32 objdist)
{
  if(spline_->GetBlobSpline()==0) return 0;
  if(spline_->GetBlobSpline()->Mode!=4) return 0;
  if(spline_->GetBlobPipe()==0) return 0;
  if(CheckMinMesh(mesh0)) return 0;

  GenMinMesh *mesh;
  BlobSpline *spline;
  BlobPipe *pipe;
  sMatrix m0,m1,mat;
  sAABox box;
  sF32 zmin[2],zmax[2];
  sInt vmin,vmax;
  sVector d;

  // prepare

  mesh = new GenMinMesh;
  mesh0->CalcBBox(box);
  zmin[0] = box.Min.z;
  zmax[0] = box.Max.z;
  if(mesh1)
    mesh1->CalcBBox(box);
  zmin[1] = box.Min.z;
  zmax[1] = box.Max.z;
  spline = spline_->GetBlobSpline();
  pipe = spline_->GetBlobPipe();
  m1.Init();

  sF32 distabs = 0;
  sVERIFY(pipe->Count*2==spline->Count);
  for(sInt seg=0;seg<pipe->Count;seg++)
  {
    // add curved

    if(seg>0)
    {
      sVector center;
      sVector axis;
      sVector d0,d1;
      BlobPipeKey *kp = &pipe->Keys[seg-1];
      BlobSplineKey *k0 = &spline->Keys[seg*2-1];
      BlobSplineKey *k1 = &spline->Keys[seg*2+0];

      d0.x = (k0->px - kp->PosX);
      d0.y = (k0->py - kp->PosY);
      d0.z = (k0->pz - kp->PosZ);
      d1.x = (k1->px - kp->PosX);
      d1.y = (k1->py - kp->PosY);
      d1.z = (k1->pz - kp->PosZ);
      center.x = kp->PosX;
      center.y = kp->PosY;
      center.z = kp->PosZ;
      d0.Unit3();
      d1.Unit3();
      axis.Cross3(d0,d1);
      sF32 gamma = sFACos(d0.Dot3(d1));
      d.Add3(d0,d1);
      d.Unit3();
      center.AddScale3(d,kp->Radius/sFCos(gamma/2));

      vmin = mesh->Vertices.Count;
      mesh->Add(mesh1 ? mesh1 : mesh0);
      vmax = mesh->Vertices.Count;
      d.Sub3(m1.l,m0.l);

      sF32 dist = kp->Radius*gamma;

      for(sInt i=vmin;i<vmax;i++)
      {
        sVector v;
        v.x = mesh->Vertices[i].Pos.x;
        v.y = mesh->Vertices[i].Pos.y;
        v.z = mesh->Vertices[i].Pos.z;
        sF32 f = (v.z-zmin[1])/(zmax[1]-zmin[1]);

        mat.InitRot(axis,-f*(sPI-gamma));
        v.z = 0;
        v.Rotate34(m1);
        v.Sub3(center);
        v.Rotate34(mat);
        v.Add3(center);
        mesh->Vertices[i].Pos.x = v.x;
        mesh->Vertices[i].Pos.y = v.y;
        mesh->Vertices[i].Pos.z = v.z;
        if((flags & 17)==17)
          mesh->Vertices[i].UV[0][1] = f*texzoom*dist+distabs;
      }
      if((flags & 24)==24)
        distabs += dist*texzoom;
    }

    // scan points

    {
      BlobSplineKey *k0 = &spline->Keys[seg*2+0];
      BlobSplineKey *k1 = &spline->Keys[seg*2+1];
      sQuaternion quat;

      quat.Init(k0->Zoom,k0->rx,k0->ry,k0->rz);
      quat.ToMatrix(m0);
      m0.l.Init4(k0->px,k0->py,k0->pz,1);

      quat.Init(k1->Zoom,k1->rx,k1->ry,k1->rz);
      quat.ToMatrix(m1);
      m1.l.Init4(k1->px,k1->py,k1->pz,1);

      // add straight

      d.Sub3(m1.l,m0.l);
      sF32 dist = d.Abs3();
      sInt middle;
      if(flags&4)
        middle = sRange<sInt>(sInt((dist/objdist)+0.5f),64,1);
      else
        middle = 1;

      for(sInt m=0;m<middle;m++)
      {
        vmin = mesh->Vertices.Count;
        mesh->Add(mesh0);
        vmax = mesh->Vertices.Count;
        for(sInt i=vmin;i<vmax;i++)
        {
          sVector v;
          v.x = mesh->Vertices[i].Pos.x;
          v.y = mesh->Vertices[i].Pos.y;
          v.z = mesh->Vertices[i].Pos.z;
          sF32 f = (v.z-zmin[0])/(zmax[0]-zmin[0]);
          f = (f+m) / middle ;
          v.z = 0;
          v.Rotate34(m0);
          v.AddScale3(d,f);
          mesh->Vertices[i].Pos.x = v.x;
          mesh->Vertices[i].Pos.y = v.y;
          mesh->Vertices[i].Pos.z = v.z;
          if(flags & 1)
            mesh->Vertices[i].UV[0][1] = f*texzoom*dist+distabs;
        }
      }
      if(flags & 8)
        distabs += dist*texzoom;

      if(mesh2)
      {
        vmin = mesh->Vertices.Count;
        mesh->Add(mesh2);
        vmax = mesh->Vertices.Count;

        for(sInt i=vmin;i<vmax;i++)
        {
          sVector v;
          v.x = mesh->Vertices[i].Pos.x;
          v.y = mesh->Vertices[i].Pos.y;
          v.z = mesh->Vertices[i].Pos.z;
          v.Rotate34(m0);
          mesh->Vertices[i].Pos.x = v.x;
          mesh->Vertices[i].Pos.y = v.y;
          mesh->Vertices[i].Pos.z = v.z;
        }

        vmin = mesh->Vertices.Count;
        mesh->Add(mesh2);
        vmax = mesh->Vertices.Count;

        for(sInt i=vmin;i<vmax;i++)
        {
          sVector v;
          v.x = mesh->Vertices[i].Pos.x;
          v.y = mesh->Vertices[i].Pos.y;
          v.z = mesh->Vertices[i].Pos.z;
          v.Rotate34(m1);
          mesh->Vertices[i].Pos.x = v.x;
          mesh->Vertices[i].Pos.y = v.y;
          mesh->Vertices[i].Pos.z = v.z;
        }

        if((flags&2) && ringdist>0.1f)
        {
          sInt middle = sInt((dist/ringdist)-0.5f);
          for(sInt i=0;i<middle;i++)
          {
            vmin = mesh->Vertices.Count;
            mesh->Add(mesh2);
            vmax = mesh->Vertices.Count;

            mat = m0;
            mat.l.AddScale3(d,(i+1.0f)/(middle+1));

            for(sInt i=vmin;i<vmax;i++)
            {
              sVector v;
              v.x = mesh->Vertices[i].Pos.x;
              v.y = mesh->Vertices[i].Pos.y;
              v.z = mesh->Vertices[i].Pos.z;
              v.Rotate34(mat);
              mesh->Vertices[i].Pos.x = v.x;
              mesh->Vertices[i].Pos.y = v.y;
              mesh->Vertices[i].Pos.z = v.z;
            }
          }
        }
      }
    }
  }

  // done;

  sRelease(spline_);
  sRelease(mesh0);
  sRelease(mesh1);
  sRelease(mesh2);

  mesh->MergeClusters();

  return mesh;
}

/****************************************************************************/

GenMinMesh * __stdcall MinMesh_Multiply(KOp *,GenMinMesh *mesh,sFSRT srt,sInt count,sInt mode,sF32 tu,sF32 tv,sF323 lrot,sF32 extrude)
{
	GenMinMesh *out;
	sMatrix xform,step,lxform,lstep,tmp;
  sVector p;

  if(CheckMinMesh(mesh)) return 0;

	step.InitSRT(srt.v);
  lstep.InitEulerPI2(&lrot.x);
	out = new GenMinMesh;
	xform.Init();
  lxform.Init();
//  if(extrude)
//    mesh->NeedAllNormals();

  sSetRndSeed(count);
	for(sInt j=0;j<count;j++)
	{
    sInt startvert = out->Vertices.Count;
		out->Add(mesh); //,!!j);   // 0:not keepmaterial: 1..n keepmaterial

    tmp.MulA(lxform,xform);

    for(sInt i=startvert;i<out->Vertices.Count;i++)
    {
      GenMinVert *v = &out->Vertices[i];
      p.x = v->Pos.x;
      p.y = v->Pos.y;
      p.z = v->Pos.z;
      p.Rotate34(tmp);
      v->Pos.x = p.x;
      v->Pos.y = p.y;
      v->Pos.z = p.z;

      if(mode&1)
      {
        v->UV[0][0] += j*tu;
        v->UV[0][1] += j*tv;
      }
    }

//    if(extrude)
//    {
//      tmp.Init();
//      tmp.i.x = j*extrude;
//      tmp.j.y = j*extrude;
//      tmp.k.z = j*extrude;
//      out->TransVert(tmp,sGMI_NORMAL,sGMI_POS | 0x10);
//    }

    if(mode&2)
      xform.InitRandomSRT(srt.v);
    else
		  xform.MulA(step);

    lxform.MulA(lstep);
	}

  mesh->Release();
  out->MergeClusters();
	return out;
}

GenMinMesh * __stdcall MinMesh_Multiply2(sInt seed,sInt3 count1,sF323 translate1,sInt3 count2,sF323 translate2,sInt random,sInt3 count3,sF323 translate3,sInt inCount,GenMinMesh *inMesh,...)
{
  if(!inCount)
    return 0;

  GenMinMesh *mesh = new GenMinMesh;
  sSetRndSeed(seed);

  sInt start = 0;
  sMatrix xform;
  xform.Init();

  if(count3.x*count3.y*count3.z * count2.x*count2.y*count2.z * count1.x*count1.y*count1.z > 1024)
  {
    count3.Init(1,1,1);
    count2.Init(1,1,1);
    count1.Init(1,1,1);
  }

  for(sInt z3=0;z3<count3.z;z3++)
  {
    for(sInt y3=0;y3<count3.y;y3++)
    {
      for(sInt x3=0;x3<count3.x;x3++)
      {

        for(sInt z2=0;z2<count2.z;z2++)
        {
          for(sInt y2=0;y2<count2.y;y2++)
          {
            for(sInt x2=0;x2<count2.x;x2++)
            {

              for(sInt z1=0;z1<count1.z;z1++)
              {
                for(sInt y1=0;y1<count1.y;y1++)
                {
                  for(sInt x1=0;x1<count1.x;x1++)
                  {
                    start = mesh->Vertices.Count;

                    GenMinMesh *in = (&inMesh)[sMin<sInt>(sGetRnd(inCount+random),inCount-1)];
                    mesh->Add(in);

                    xform.l.x = x1 * translate1.x + x2 * translate2.x + x3 * translate3.x;
                    xform.l.y = y1 * translate1.y + y2 * translate2.y + y3 * translate3.y;
                    xform.l.z = z1 * translate1.z + z2 * translate2.z + z3 * translate3.z;

                    for(sInt i=start;i<mesh->Vertices.Count;i++)
                    {
                      mesh->Vertices[i].Pos.x += xform.l.x;
                      mesh->Vertices[i].Pos.y += xform.l.y;
                      mesh->Vertices[i].Pos.z += xform.l.z;
                    }
                  }
                }
              }

            }
          }
        }

      }
    }
  }

  for(sInt i=0;i<inCount;i++)
    (&inMesh)[i]->Release();

  return mesh;
}

/****************************************************************************/

GenMinMesh * __stdcall MinMesh_Center(GenMinMesh *mesh,sInt which)
{
  sAABox box;
  sF32 tx,ty,tz;

  if(CheckMinMesh(mesh)) return 0;

  mesh->CalcBBox(box);
  tx = (which&1)?(box.Max.x+box.Min.x)/2:0;
  ty = (which&2)?(box.Max.y+box.Min.y)/2:0;
  tz = (which&4)?(box.Max.z+box.Min.z)/2:0;
  for(sInt i=0;i<mesh->Vertices.Count;i++)
  {
    mesh->Vertices[i].Pos.x -= tx;
    mesh->Vertices[i].Pos.y -= ty;
    mesh->Vertices[i].Pos.z -= tz;
  }
  return mesh;
}

/****************************************************************************/

GenMinMesh * __stdcall MinMesh_RenderAutoMap(GenMinMesh *mesh,sInt flags)
{
  sAABox box;
  GenMinFace *face;
  GenMinVert *vert;
  if(CheckMinMesh(mesh)) return 0;
  sF32 map[6][6];
  sF32 uv[6][2];
  sMatrix mat;
  sVector min,max;
  sInt fields[6];
  sInt fieldmax;

  mesh->CalcNormals();
  mesh->CalcBBox(box);

  fieldmax = 0;
  for(sInt i=0;i<6;i++)
  {
    fields[i] = -1;
    if(flags & (1<<i))
      fields[i] = fieldmax++;
    else
      fields[i] = -1;
  }

  if(fields[0]==-1 && fields[1]>=0) fields[0] = fields[1];    // provide good defaults for unspecified directions
  if(fields[0]==-1 && fields[4]>=0) fields[0] = fields[4];
  if(fields[0]==-1 && fields[5]>=0) fields[0] = fields[5];
  if(fields[0]==-1 && fields[2]>=0) fields[0] = fields[2];
  if(fields[0]==-1 && fields[3]>=0) fields[0] = fields[3];
  if(fields[1]==-1)                 fields[1] = fields[0];
  if(fields[4]==-1 && fields[5]>=0) fields[4] = fields[5];
  if(fields[4]==-1)                 fields[4] = fields[0];
  if(fields[5]==-1)                 fields[5] = fields[4];
  if(fields[2]==-1 && fields[3]>=0) fields[2] = fields[3];
  if(fields[2]==-1)                 fields[2] = fields[0];
  if(fields[3]==-1)                 fields[3] = fields[2];

  for(sInt i=0;i<mesh->Vertices.Count;i++)
    mesh->Vertices[i].TempByte=-1;

  for(sInt i=0;i<mesh->Faces.Count;i++)
  {
    face = &mesh->Faces[i];
    face->Temp = face->Normal.Classify()^1;  
    for(sInt j=0;j<face->Count;j++)
      mesh->Vertices[face->Vertices[j]].TempByte = face->Temp;
  }

  for(sInt i=0;i<6;i++)
  {
    mat.InitClassify(i);
    min.Rotate34(mat,box.Min);
    max.Rotate34(mat,box.Max);
    
    map[i][0] = mat.i.x / sFAbs(max.x-min.x) * (1.0f/fieldmax);
    map[i][1] = mat.i.y / sFAbs(max.y-min.y) * (1.0f/fieldmax);
    map[i][2] = mat.i.z / sFAbs(max.z-min.z) * (1.0f/fieldmax);
    map[i][3] = mat.j.x / sFAbs(max.x-min.x);
    map[i][4] = mat.j.y / sFAbs(max.y-min.y);
    map[i][5] = mat.j.z / sFAbs(max.z-min.z);

//    if(min.x>max.x) sSwap(min.x,max.x);
//    if(min.y>max.y) sSwap(min.y,max.y);
//    if(min.z>max.z) sSwap(min.z,max.z);

    uv[i][0] = -(map[i][0]*min.x + map[i][1]*min.y + map[i][2]*min.z) + (1.0f*fields[i]/fieldmax);
    uv[i][1] = -(map[i][3]*min.x + map[i][4]*min.y + map[i][5]*min.z);
  }

  for(sInt i=0;i<mesh->Vertices.Count;i++)
  {
    vert = &mesh->Vertices[i];
    sVERIFY(vert->TempByte != -1);
    vert->UV[0][0] = map[vert->TempByte][0]*vert->Pos.x 
                   + map[vert->TempByte][1]*vert->Pos.y
                   + map[vert->TempByte][2]*vert->Pos.z
                   + uv[vert->TempByte][0];
    vert->UV[0][1] = map[vert->TempByte][3]*vert->Pos.x 
                   + map[vert->TempByte][4]*vert->Pos.y
                   + map[vert->TempByte][5]*vert->Pos.z
                   + uv[vert->TempByte][1];
    vert->UV[0][1] = 1-vert->UV[0][1];
  }

  return mesh;
}

/****************************************************************************/
/****************************************************************************/
