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

#include "geneffect.hpp"
#include "genmaterial.hpp"
#include "genbitmap.hpp"
#include "genmesh.hpp"
#include "genminmesh.hpp"
#include "kkriegergame.hpp"
#include "genoverlay.hpp"
#include "engine.hpp"
#include "material11.hpp"
#include "_util.hpp"
#include "_startdx.hpp"
#include "geneffectex.hpp"
#include "geneffectcubes.hpp"
#include <xmmintrin.h>

extern sF32 GlobalFps;


/****************************************************************************/
/****************************************************************************/
/***                                                                      ***/
/***   Effekte ohne besonderen Grund...                                   ***/
/***                                                                      ***/
/****************************************************************************/
/****************************************************************************/


void SetVert2(sF32 *fp,sF32 x,sF32 y,sF32 z,sF32 u,sF32 v,sU32 col=~0)
{
  fp[0] = x;
  fp[1] = y;
  fp[2] = z;
  fp[3] = 0;
  fp[4] = 0;
  *((sU32 *)(&fp[5])) = col;
  fp[6] = u;
  fp[7] = v;
}

/****************************************************************************/
/***                                                                      ***/
/***   Starter-Effekt.                                                    ***/
/***                                                                      ***/
/***   Für copy & paste                                                   ***/
/***                                                                      ***/
/****************************************************************************/

void Chaos0(KOp *op,KEnvironment *kenv,sF323 a,sF323 b,sF323 c,sInt flags,sInt effect,sInt seed,sInt count)
{
  GenMaterial *mtrl;
  sInt geo;
  sU16 *ip;
  sVertexDouble *vp;
  sInt i;

  mtrl = (GenMaterial *) op->GetLinkCache(0);
  if(!(mtrl && mtrl->Passes.Count>0)) return;

  geo = sSystem->GeoAdd(sFVF_DOUBLE,sGEO_TRI);
  mtrl->Passes[0].Mtrl->Set(kenv->CurrentCam);
  sSystem->GeoBegin(geo,4*count,6*count,(sF32 **)&vp,(void **)&ip);

  for(i=0;i<count;i++)
  {
    vp[0+i*4].Init(-1,-1,i*a.z,~0,0,0,0,0);
    vp[1+i*4].Init( 1,-1,i*a.z,~0,0,1,0,1);
    vp[2+i*4].Init(-1, 1,i*a.z,~0,1,0,1,0);
    vp[3+i*4].Init( 1, 1,i*a.z,~0,1,1,1,1);
  }
  for(i=0;i<count;i++)
  {
    sQuad(ip,i*4+0,i*4+1,i*4+3,i*4+2);
  }
  sSystem->GeoEnd(geo);
  sSystem->GeoDraw(geo);
  sSystem->GeoRem(geo);
}

/****************************************************************************/
/***                                                                      ***/
/***   Einige sich anziehende Gravitationsquellen.                        ***/
/***                                                                      ***/
/***   Die Simulation wird jeden Frame mit neuen Startwerten durchgeführt.***/
/***   Aus den Bewegungsbahnen werden Bänder dargestellt.                 ***/
/***                                                                      ***/
/***   Divisionen durch Null werden noch nicht abgefangen.                ***/
/***                                                                      ***/
/****************************************************************************/

void Chaos1(KOp *op,KEnvironment *kenv,sF323 a,sF323 b,sF323 c,sInt flags,sInt effect,sInt seed,sInt maxcount)
{
  const sInt pcount = 8;
  const sInt steps = 250;
  GenMaterial *mtrl;
  sInt geo;
  sF32 *fp;
  sU16 *ip;
  sInt i,j,k;
  sF32 s;
  sMatrix Pos[pcount],mat;
  sVector Speed[pcount];
  sVector d;
  sF32 dist,t;
  sU32 col;
  sInt invert;

  static sMatrix record[pcount][steps];

  mtrl = (GenMaterial *) op->GetLinkCache(0);
  if(!(mtrl && mtrl->Passes.Count>0)) return;

  geo = sSystem->GeoAdd(sFVF_TSPACE3,sGEO_TRI);
  s = 0.25f;

  for(invert = -1;invert<=1;invert+=2)
  {
    sSetRndSeed(seed);
    t = a.z+24;
    for(i=0;i<pcount;i++)
    {
      Pos[i].Init();
      Pos[i].l.x = sFSin(t*sFGetRnd())*c.x;
      Pos[i].l.y = sFSin(t*sFGetRnd())*c.y;
      Pos[i].l.z = sFSin(t*sFGetRnd())*c.z;
      Pos[i].l.w = 1.0f;
      Pos[i].k.InitRnd();
      Pos[i].k.x *= b.x*invert;
      Pos[i].k.y *= b.y*invert;
      Pos[i].k.z *= b.z*invert;
      Pos[i].j.Init(0,0,0,0);
    }
    Pos[0].Init();
    Speed[0].Init(0,0,0,0);

    for(i=0;i<steps;i++)
    {
      for(j=0;j<pcount-1;j++)
      {
        for(k=j+1;k<pcount;k++)
        {
          d.Sub3(Pos[j].l,Pos[k].l);
          dist = d.Dot3(d);
          d.Scale3(dist);

          Pos[j].j.Sub3(d);
          Pos[k].j.Add3(d);
        }
      }
      Pos[0].Init();
      Speed[0].Init(0,0,0,0);
      for(j=0;j<pcount;j++)
      {
        record[j][i] = Pos[j];
        Pos[j].k.AddScale3(Pos[j].j,a.x*0.0001f);
        Pos[j].l.AddScale3(Pos[j].k,a.y*0.01f);
        Pos[j].j.Init(0,0,0,0);
      }
    }

    for(j=1;j<pcount;j++)
    {
      mtrl->Passes[0].Mtrl->Set(kenv->CurrentCam);
      sSystem->GeoBegin(geo,steps*2,(steps-1)*6,&fp,(void **)&ip);
      for(i=0;i<steps;i++)
      {
        mat = record[j][i];
//        s = mat.j.Abs3()*0.00003f;//sRange<sF32>(2-mat.j.Abs3(),2,1)*0.25f;
        mat.k.Unit3();
        mat.i.Cross3(mat.j,mat.k);
        mat.i.Unit3();
        mat.j.Cross3(mat.k,mat.i);
        col = 255-(i*255/steps);
        col = 0xff000000|(col<<16)|(col<<8)|(col<<0);
        SetVert2(fp,mat.l.x+mat.i.x*s,mat.l.y+mat.i.y*s,mat.l.z+mat.i.z*s,0,i*1.0f*invert,col);
        fp += 8;
        SetVert2(fp,mat.l.x-mat.i.x*s,mat.l.y-mat.i.y*s,mat.l.z-mat.i.z*s,1,i*1.0f*invert,col);
        fp += 8;
      }
      for(i=0;i<steps-1;i++)
      {
        sQuad(ip,i*2+0,i*2+1,i*2+3,i*2+2);
      }
      sSystem->GeoEnd(geo);
      sSystem->GeoDraw(geo);
    }
  }

  sSystem->GeoRem(geo);
}

/****************************************************************************/
/***                                                                      ***/
/***   UV-Animator                                                        ***/
/***                                                                      ***/
/***   Ein Quad wird mehrfach gezeichnet.                                 ***/
/***   Dabei werden zwei paare UV-Koordinaten rotiert, verschoben und     ***/
/***   skaliert.                                                          ***/
/***   Man sollte damit zwei Texturen multiplizieren und additiv blenden. ***/
/***                                                                      ***/
/****************************************************************************/

void Chaos2(KOp *op,KEnvironment *kenv,sF323 a,sF323 b,sF323 c,sInt flags,sInt effect,sInt seed,sInt maxcount)
{
  GenMaterial *mtrl;

  sInt geo;
  sU16 *ip;
  sInt i;
  sInt steps;
  sVertexDouble *vp;
  sF32 s0,c0,s1,c1;
  sF32 r0,r1,z;

  mtrl = (GenMaterial *) op->GetLinkCache(0);
  if(!(mtrl && mtrl->Passes.Count>0)) return;

  steps = maxcount;
  geo = sSystem->GeoAdd(sFVF_DOUBLE,sGEO_TRI);
  mtrl->Passes[0].Mtrl->Set(kenv->CurrentCam);
  sSystem->GeoBegin(geo,steps*4,(steps)*6,(sF32 **)&vp,(void **)&ip);

  r0 = a.x;
  r1 = a.y;
  z  = a.z;

  for(i=0;i<steps;i++)
  {
    s0 = sFSin(r0)*z;
    c0 = sFCos(r0)*z;
    s1 = sFSin(-r0)*z;
    c1 = sFCos(-r0)*z;

    vp[0+i*4].Init(-1,-1,0,~0, s0+r1, c0, s1-r1, c1);
    vp[1+i*4].Init( 1,-1,0,~0, c0+r1,-s0, c1-r1,-s1);
    vp[2+i*4].Init(-1, 1,0,~0,-c0+r1, s0,-c1-r1, s1);
    vp[3+i*4].Init( 1, 1,0,~0,-s0+r1,-c0,-s1-r1,-c1);

    r0 += b.x/steps;
    r1 += b.y/steps;
    z  += b.z/steps;
  }
  for(i=0;i<steps;i++)
  {
    sQuad(ip,i*4+0,i*4+1,i*4+3,i*4+2);
  }
  sSystem->GeoEnd(geo);
  sSystem->GeoDraw(geo);
  sSystem->GeoRem(geo);
}

/****************************************************************************/
/***                                                                      ***/
/***   Plane-Partition                                                    ***/
/***                                                                      ***/
/***   Unterteile eine ebene immer wieder.                                ***/
/***                                                                      ***/
/****************************************************************************/

struct Chaos3Cell
{
  sVector Vertex[4];
  sInt Level;
  sInt Leaf;
  sU32 Color;
  sF32 Wx,Wy;
};


void Chaos3(KOp *op,KEnvironment *kenv,sF323 a,sF323 b,sF323 c,sInt flags,sInt effect,sInt seed,sInt count)
{
  GenMaterial *mtrl;
  sInt geo;
  sU16 *ip;
  sVertexDouble *vp;
  sInt i,j,cc,cd;
  const sInt maxcell = 1024;
  sInt max;
  Chaos3Cell *cp,*ca,*cb;
  static Chaos3Cell cells[maxcell];
  sVector v1,v2,v;
  sVector vd[4];
  sF32 f,fa,fb;
  sU32 col;

  mtrl = (GenMaterial *) op->GetLinkCache(0);
  if(!(mtrl && mtrl->Passes.Count>0)) return;
  sSetRndSeed(seed);

// generate cells

  cc = 0;
  cd = 0;
  cp = &cells[cc++];
  cp->Level = 0;
  cp->Leaf = 0;
  cp->Color = sGetRnd()|0xff808080;
  cp->Wx = 1.0f;
  cp->Wy = 1.0f;
  cp->Vertex[0].Init(-16,0,-16,1);
  cp->Vertex[1].Init( 16,0,-16,1);
  cp->Vertex[2].Init( 16,0, 16,1);
  cp->Vertex[3].Init(-16,0, 16,1);

  for(;cc<count && cc<maxcell-10 && cd<=cc;)
  {
    cp = &cells[cd++];       // divide this cell

    if(cp->Level>=2 && sFGetRnd()<a.y)  continue;    // don't divide this cell..

    ca = &cells[cc++];      // the two new cells..
    cb = &cells[cc++];

    *ca = *cp;
    *cb = *cp;
    cp->Leaf = 0;
    ca->Level++;
    ca->Leaf = 1;
    ca->Color = sGetRnd()|0xff808080;
    cb->Level++;
    cb->Leaf = 1;
      cb->Color = sGetRnd()|0xff808080;

    v1.Sub3(cp->Vertex[0],cp->Vertex[3]);
    v1.Add3(cp->Vertex[1]);
    v1.Sub3(cp->Vertex[2]);
    v2.Sub3(cp->Vertex[0],cp->Vertex[1]);
    v2.Add3(cp->Vertex[3]);
    v2.Sub3(cp->Vertex[2]);
    fa = v1.Abs3();
    fb = v2.Abs3();

    if(cp->Wx/cp->Wy>1.0f+(sFGetRnd(2)-1.0f)*a.z)         // by x or z axxis?
    {
      fa = sFGetRnd(b.x)+(1.0f-b.x)/2;
      fb = (sFGetRnd(1)-0.5f)*(b.y*2/(cp->Level+2));
      v1.Lin3(cp->Vertex[0],cp->Vertex[1],fa+fb);
      v2.Lin3(cp->Vertex[3],cp->Vertex[2],fa-fb);
      ca->Vertex[0] = v1;
      ca->Vertex[3] = v2;
      cb->Wx = cp->Wx*fa;
      cb->Vertex[1] = v1;
      cb->Vertex[2] = v2;
      ca->Wx = cp->Wx*(1-fa);
    }
    else
    {
      fa = sFGetRnd(b.x)+(1.0f-b.x)/2;
      fb = (sFGetRnd(1)-0.5f)*(b.y*2/(cp->Level+2));
      v1.Lin3(cp->Vertex[0],cp->Vertex[3],fa+fb);
      v2.Lin3(cp->Vertex[1],cp->Vertex[2],fa-fb);
      ca->Vertex[0] = v1;
      ca->Vertex[1] = v2;
      cb->Wy = cp->Wx*fa;
      cb->Vertex[3] = v1;
      cb->Vertex[2] = v2;
      ca->Wy = cp->Wx*(1-fa);
    }
  }

// draw cells

  max = cc;
  geo = sSystem->GeoAdd(sFVF_DOUBLE,sGEO_TRI);
  mtrl->Passes[0].Mtrl->Set(kenv->CurrentCam);
  sSystem->GeoBegin(geo,4*max,6*max,(sF32 **)&vp,(void **)&ip);

  for(i=0;i<max;i++)
  {
    cp = &cells[i];
    
    col = cp->Color;
    vp[0+i*4].Init(0,0,0,col,0,1,0,0);
    vp[1+i*4].Init(0,0,0,col,1,1,0,0);
    vp[2+i*4].Init(0,0,0,col,1,0,0,0);
    vp[3+i*4].Init(0,0,0,col,0,0,0,0);

//    f = cp->Level*a.x;
    f = a.x;
    for(j=0;j<4;j++)
    {
      v.Sub3(cp->Vertex[j],cp->Vertex[(j+1)&3]);  
      v.Unit3();
      vd[j].Init(v.z*f,0,-v.x*f,0);
    }

    for(j=0;j<4;j++)
    {
      vp[j+i*4].x = cp->Vertex[j].x+vd[(j+3)&3].x+vd[(j+0)&3].x;
      vp[j+i*4].y = cp->Level*0.1;
      vp[j+i*4].z = cp->Vertex[j].z+vd[(j+3)&3].z+vd[(j+0)&3].z;
    }
  }
  for(i=0;i<max;i++)
  {
    sQuad(ip,i*4+0,i*4+1,i*4+2,i*4+3);
  }
  sSystem->GeoEnd(geo);
  sSystem->GeoDraw(geo);
  sSystem->GeoRem(geo);
}

/****************************************************************************/
/***                                                                      ***/
/***   Der gute alte Timeslice-Effect (Rubber Cube)                       ***/
/***                                                                      ***/
/***   ... nur dass ich dies mal alle phasen jeden frame neu zeichne,     ***/
/***   in den selben framebuffer (additiv), und mit einer projezierten    ***/
/***   textur das ding in scheiben schneide - World Space                 ***/
/***                                                                      ***/
/***   Ich habe also volle Kontrolle über alle Bewegungen, kann das       ***/
/***   Objekt morphen und alles einfrieren und im 3d-Raum herumfliegen... ***/
/***                                                                      ***/
/****************************************************************************/

void Chaos4(KOp *op,KEnvironment *kenv,sF323 a,sF323 b,sF323 c,sInt flags,sInt effect,sInt seed,sInt count)
{
  GenMaterial *mtrl;
  sInt geo;
  sU16 *ip;
  volatile sVertexDouble *vp;
  sInt i;
  sInt tsx,tsy,ttt,tsl;
  sInt x,y,t,s;
  sF32 ri,ro,fx,fy,scale;
  sVector v;
  sMatrix mat;
  static sVector cache[4096];
  sVector *p;

  mtrl = (GenMaterial *) op->GetLinkCache(0);
  if(!(mtrl && mtrl->Passes.Count>0)) return;

  tsl = count;
  ttt = 4;
  tsx = 12;
  tsy = 24;

  sVERIFY(ttt*(tsx+1)*(tsy+1)<4096);
  p = cache;
  for(t=0;t<ttt;t++)
  {
    ro = 1.00f+t*0.50f;
    ri = 0.20f;

    for(y=0;y<tsy+1;y++)
    {
      for(x=0;x<tsx+1;x++)
      {
        fx = x*sPI2F/tsx;
        fy = y*sPI2F/tsy;

        v.x = sFSin(fy)*(ro+sFSin(fx)*ri);
        v.y = sFCos(fx)*ri;
        v.z = sFCos(fy)*(ro+sFSin(fx)*ri);
        v.w = 1.0f;

        *p++ = v;
      }
    }
  }

  geo = sSystem->GeoAdd(sFVF_DOUBLE,sGEO_TRI);

  for(s=0;s<tsl;s++)
  {
    scale = 2;
    sMaterial11 *mtrl11 = (sMaterial11 *) mtrl->Passes[0].Mtrl;
    mtrl11->SRT1[1] = 0.5f/(scale/(tsl-1.0f));
    mtrl11->SRT1[7] = mtrl11->SRT1[1]*(s/(tsl-1.0f)*scale-(scale/2))+0.5f;
    mtrl11->Set(kenv->CurrentCam);
    sSystem->GeoBegin(geo,(tsx+1)*(tsy+1)*ttt,6*tsx*tsy*ttt,(sF32 **)&vp,(void **)&ip);

    p = cache;
    for(t=0;t<ttt;t++)
    {
      ro = 1.00f+t*0.50f;
      ri = 0.20f;

      mat.InitEuler(
        a.x+b.x*t/ttt+c.x*s/tsl,
        a.y+b.y*t/ttt+c.y*s/tsl,
        a.z+b.z*t/ttt+c.z*s/tsl);

      for(y=0;y<tsy+1;y++)
      {
        for(x=0;x<tsx+1;x++)
        {
          v = *p++;
          v.Rotate34(mat);
          vp->x = v.x;
          vp->y = v.y;
          vp->z = v.z;
          vp->c0 = ~0;
          vp->u0 = x*1.0f/tsx;
          vp->v0 = y*1.0f/tsy;
          vp->u1 = 0;
          vp->v1 = 0;
          vp++;
        }
      }
    }
    i = 0;
    for(t=0;t<ttt;t++)
    {
      for(y=0;y<tsy;y++)
      {
        for(x=0;x<tsx;x++)
        {
          sQuad(ip,
            t*(tsx+1)*(tsy+1)+(y+0)*(tsx+1)+(x+0),
            t*(tsx+1)*(tsy+1)+(y+1)*(tsx+1)+(x+0),
            t*(tsx+1)*(tsy+1)+(y+1)*(tsx+1)+(x+1),
            t*(tsx+1)*(tsy+1)+(y+0)*(tsx+1)+(x+1));
        }
      }
    }
    sSystem->GeoEnd(geo);
    sSystem->GeoDraw(geo);
  }
  sSystem->GeoRem(geo);
}


/****************************************************************************/
/***                                                                      ***/
/***   Allgemeiner code für die Effekte                                   ***/
/***                                                                      ***/
/****************************************************************************/

KObject * __stdcall Init_Effect_Chaos1(class GenMaterial *mtrl,sF323 a,sF323 b,sF323 c,sInt flags,sInt effect,sInt seed,sInt maxcount)
{
  if(mtrl) mtrl->Release();
  return new GenEffect;
}

void __stdcall Exec_Effect_Chaos1(KOp *op,KEnvironment *kenv,sF323 a,sF323 b,sF323 c,sInt flags,sInt effect,sInt seed,sInt maxcount)
{
  switch(effect)
  {
  case 0:
    Chaos0(op,kenv,a,b,c,flags,effect,seed,maxcount);
    break;
  case 1:
    Chaos1(op,kenv,a,b,c,flags,effect,seed,maxcount);
    break;
  case 2:
    Chaos2(op,kenv,a,b,c,flags,effect,seed,maxcount);
    break;
  case 3:
    Chaos3(op,kenv,a,b,c,flags,effect,seed,maxcount);
    break;
  case 4:
    Chaos4(op,kenv,a,b,c,flags,effect,seed,maxcount);
    break;
  }
}


/****************************************************************************/
/***                                                                      ***/
/***   Tourque Effect                                                     ***/
/***                                                                      ***/
/****************************************************************************/

#if !sINTRO

struct sVector3
{
  sF32 x,y,z;

  void Init(const sVector &v)
  {
    x = v.x;
    y = v.y;
    z = v.z;
  }
};

sMAKEZONE(TourquePart,"TourquePart",0x400000);
sMAKEZONE(TourqueCons,"TourqueCons",0x600000);
sMAKEZONE(TourqueDraw,"TourqueDraw",0x800000);

class TourqueSimulator
{
  enum {
    FieldDim = 32,
    MaskField = FieldDim - 1,
    yStep = FieldDim,
    zStep = FieldDim*FieldDim,
  };

public:
  struct Constraint
  {
    sInt p0,p1;     // particle indices
    sF32 restLenSq; // squared rest length
  };

  sArray<Constraint> Constraints;

private:
  sVector3 *ForceField;
  sInt StepTimer;

  // ---- particles
  static sF32 __forceinline SmoothStep(sF32 x)
  {
    return x*x*(3.0f - 2.0f*x);
  }

  static void __forceinline xLerp(sVector3 &out,const sVector3 *base,sInt d,sF32 t)
  {
    out.x = base->x + t * (base[d].x - base->x);
    out.y = base->y + t * (base[d].y - base->y);
    out.z = base->z + t * (base[d].z - base->z);
  }

  static void __forceinline vLerp(sVector3 &out,const sVector3 &a,const sVector3 &b,sF32 t)
  {
    out.x = a.x + t * (b.x - a.x);
    out.y = a.y + t * (b.y - a.y);
    out.z = a.z + t * (b.z - a.z);
  }

  void MoveParticles(sF32 speed,sF32 damp)
  {
    sZONE(TourquePart);

    sVector3 *oldPos,*newPos;

    oldPos = ParticlePos[0];
    newPos = ParticlePos[1];

    sF32 scale = 4.0f, offset = scale * FieldDim / 2.0f;
    sF32 magic = 12582912.0f; // 1.5*2^23

    // process each particle
    for(sInt i=0;i<ParticleCount;i++,oldPos++,newPos++)
    {
      // position
      sF32 x = oldPos->x * scale + offset;
      sF32 y = oldPos->y * scale + offset;
      sF32 z = oldPos->z * scale + offset;

      // rounded version, fractional part
      sF32 rx = (magic + x) - magic, fx = SmoothStep(x - rx);
      sF32 ry = (magic + y) - magic, fy = SmoothStep(y - ry);
      sF32 rz = (magic + z) - magic, fz = SmoothStep(z - rz);

      // integer grid position
      sInt ix = sInt(rx) & MaskField, nx = (ix + 1) & MaskField;
      sInt iy = sInt(ry) & MaskField, ny = (iy + 1) & MaskField;
      sInt iz = sInt(rz) & MaskField, nz = (iz + 1) & MaskField;
      const sVector3 *field = ForceField + ix + iy * yStep + iz * zStep;
      
      sInt deltaX = nx - ix;
      sInt deltaY = (ny - iy) * yStep, deltaZ = (nz - iz) * zStep;

      // calc force
      sVector3 t0,t1,t2,force;

      xLerp(t0,field +      0 +      0,deltaX,fx);
      xLerp(t1,field +      0 + deltaY,deltaX,fx);
      vLerp(t0,t0,t1,fy);
      xLerp(t1,field + deltaZ +      0,deltaX,fx);
      xLerp(t2,field + deltaZ + deltaY,deltaX,fx);
      vLerp(t1,t1,t2,fy);
      vLerp(force,t0,t1,fz);

      // verlet integration
      newPos->x = oldPos->x + (oldPos->x - newPos->x)*damp + force.x*speed;
      newPos->y = oldPos->y + (oldPos->y - newPos->y)*damp + force.y*speed;
      newPos->z = oldPos->z + (oldPos->z - newPos->z)*damp + force.z*speed;
    }
  }

  void Constrain()
  {
    sZONE(TourqueCons);

    Constraint *cons = Constraints.Array;
    sVector3 *parts = ParticlePos[1];

    // enforce stick constraints
    for(sInt i=0;i<Constraints.Count;i++,cons++)
    {
      if(cons->p0 >= ParticleCount || cons->p1 >= ParticleCount)
        continue;

      // get particles
      sVector3 *p0 = parts + cons->p0;
      sVector3 *p1 = parts + cons->p1;

      // difference, squared distance
      sF32 dx = p1->x - p0->x, dy = p1->y - p0->y, dz = p1->z - p0->z;
      sF32 dsq = dx*dx + dy*dy + dz*dz;

      // approximately enforce constraint
      sF32 sc = 0.5f - cons->restLenSq / (dsq + cons->restLenSq);

      p0->x += dx * sc; p1->x -= dx * sc;
      p0->y += dy * sc; p1->y -= dy * sc;
      p0->z += dz * sc; p1->z -= dz * sc;
    }
  }

  void TimeStep(sF32 speed,sF32 damp)
  {
    // swap old and new particles
    sSwap(ParticlePos[0],ParticlePos[1]);

    MoveParticles(speed,damp);
    Constrain();
  }

  // ---- force field
  __forceinline sInt Index(sInt x,sInt y,sInt z)
  {
    return (x & MaskField) + (y & MaskField) * yStep + (z & MaskField) * zStep;
  }

  __forceinline sVector3 &Field(sInt x,sInt y,sInt z)
  {
    return ForceField[Index(x,y,z)];
  }

  void PreprocessField(sF32 postScale = 0.001f)
  {
    // alloc temp space
    sF32 *div = new sF32[FieldDim*FieldDim*FieldDim];
    sF32 *high = new sF32[FieldDim*FieldDim*FieldDim];

    // calc divergence field
    sF32 scale = 1.0f / FieldDim;
    sF32 invScale = 1.0f / scale;

    for(sInt z=0;z<FieldDim;z++)
    {
      for(sInt y=0;y<FieldDim;y++)
      {
        for(sInt x=0;x<FieldDim;x++)
        {
          div[Index(x,y,z)] = -0.5f * scale *
            (Field(x+1,y,z).x - Field(x-1,y,z).x
            +Field(x,y+1,z).y - Field(x,y-1,z).y
            +Field(x,y,z+1).z - Field(x,y,z-1).z);
          high[Index(x,y,z)] = 0.0f;
        }
      }
    }

    // gauss-seidel iteration to calc density field
    for(sInt step=0;step<40;step++)
    {
      for(sInt z=0;z<FieldDim;z++)
      {
        for(sInt y=0;y<FieldDim;y++)
        {
          for(sInt x=0;x<FieldDim;x++)
          {
            high[Index(x,y,z)] = (-6 * div[Index(x,y,z)]
              + high[Index(x-1,y,z)] + high[Index(x+1,y,z)]
              + high[Index(x,y-1,z)] + high[Index(x,y+1,z)]
              + high[Index(x,y,z-1)] + high[Index(x,y,z+1)]) / 6.0f;
          }
        }
      }
    }

    // remove gradients from vector field
    for(sInt z=0;z<FieldDim;z++)
    {
      for(sInt y=0;y<FieldDim;y++)
      {
        for(sInt x=0;x<FieldDim;x++)
        {
          Field(x,y,z).x -= 0.5f * invScale * (high[Index(x+1,y,z)] - high[Index(x-1,y,z)]);
          Field(x,y,z).y -= 0.5f * invScale * (high[Index(x,y+1,z)] - high[Index(x,y-1,z)]);
          Field(x,y,z).z -= 0.5f * invScale * (high[Index(x,y,z+1)] - high[Index(x,y,z-1)]);

          Field(x,y,z).x *= postScale;
          Field(x,y,z).y *= postScale;
          Field(x,y,z).z *= postScale;
        }
      }
    }

    // free temp space
    delete[] div;
    delete[] high;
  }

  void RandomField(sF32 strength = 1.0f)
  {
    // make some random vector field
    for(sInt i=0;i<FieldDim*FieldDim*FieldDim;i++)
    {
      sVector v;
      v.InitRnd();
      v.UnitSafe3();
      v.Scale3(strength);

      ForceField[i].x = v.x;
      ForceField[i].y = v.y;
      ForceField[i].z = v.z;
    }
  }

public:
  enum
  {
    MaxParticles = 0x40000,
  };

  sVector3 *ParticlePos[2];    // 0=old, 1=new (pointers get swapped)
  sInt ParticleCount;         // # of live particles
  sInt TourqueIndex;          // which particle to delete
  sInt TourqueCount;          // max particle count
  sInt TourquePCount;
  sInt TourqueMode;
  sInt LastTime;
  sInt OldSeed;

  TourqueSimulator(sInt max)
  {
    Constraints.Init();
    TourqueCount = max;
    TourqueIndex = 0;
    TourqueMode = -1;
    LastTime = 0;
    OldSeed = -1;
    ForceField = new sVector3[FieldDim*FieldDim*FieldDim];
    ResetField();

    ParticlePos[0] = new sVector3[MaxParticles];
    ParticlePos[1] = new sVector3[MaxParticles];
    ParticleCount = 0;
    StepTimer = 0;

    sSetMem(ParticlePos[0],0,MaxParticles*sizeof(sVector3));
    sSetMem(ParticlePos[1],0,MaxParticles*sizeof(sVector3));
  }

  ~TourqueSimulator()
  {
    Constraints.Exit();
    delete[] ForceField;
    delete[] ParticlePos[0];
    delete[] ParticlePos[1];
  }

  sF32 Advance(sInt ticks,sF32 speed,sF32 damp)
  {
    ticks = sMin(ticks,500); // to limit time spent in simulation
    StepTimer += ticks * 32;
    
    while(StepTimer >= 1000)
    {
      TimeStep(speed,damp);
      StepTimer -= 1000;
    }

    return StepTimer / 1000.0f;
  }

  void ClearConstraints()
  {
    Constraints.Count = 0;
  }

  void AddConstraint(sInt p0,sInt p1,sF32 len)
  {
    Constraint *cons = Constraints.Add();
    cons->p0 = p0;
    cons->p1 = p1;
    cons->restLenSq = len*len;
  }

  void ResetField()
  {
    RandomField();
    PreprocessField();
  }

  void MeshField(GenMesh *mesh,sF32 strength)
  {
    // clear force field
    RandomField(0.001f * strength / 40.0f);
    // twirlify
    PreprocessField(0.001f * strength / 80.0f);

    // calc density field
    sVector *vb = mesh->VertBuf;
    sInt step = mesh->VertSize();
    sInt count = mesh->Vert.Count;
    sF32 *dens = new sF32[FieldDim*FieldDim*FieldDim];

    for(sInt z=0;z<FieldDim;z++)
    {
      for(sInt y=0;y<FieldDim;y++)
      {
        for(sInt x=0;x<FieldDim;x++)
        {
          sVector pos;

          pos.x = (x - FieldDim / 2.0f) / 4.0f;
          pos.y = (y - FieldDim / 2.0f) / 4.0f;
          pos.z = (z - FieldDim / 2.0f) / 4.0f;

          sF32 minDSq = 1e+20f;
          sF32 density = 0.0f;

          for(sInt v=0;v<count;v++)
          {
            const sVector &vp = vb[v];
            sF32 dx = vp.x - pos.x;
            sF32 dy = vp.y - pos.y;
            sF32 dz = vp.z - pos.z;
            sF32 d = dx*dx + dy*dy + dz*dz;
            //minDSq = sMin(d,minDSq);

            density += strength * sMin(1.0f / (d + 0.01f),8.0f);
          }

          /*minDSq += pos.x*pos.x + pos.y*pos.y + pos.z*pos.z;
          dens[Index(x,y,z)] = strength * sMin(1.0f / (minDSq + 0.01f),8.0f);*/
          dens[Index(x,y,z)] = density / count;
        }
      }
    }

    // convert density field to forces
    for(sInt z=0;z<FieldDim;z++)
    {
      for(sInt y=0;y<FieldDim;y++)
      {
        for(sInt x=0;x<FieldDim;x++)
        {
          Field(x,y,z).x -= 0.5f * (dens[Index(x+1,y,z)] - dens[Index(x-1,y,z)]);
          Field(x,y,z).y -= 0.5f * (dens[Index(x,y+1,z)] - dens[Index(x,y-1,z)]);
          Field(x,y,z).z -= 0.5f * (dens[Index(x,y,z+1)] - dens[Index(x,y,z-1)]);
        }
      }
    }

    delete[] dens;
  }
};

struct TourquePart
{
  sVector Pos;
  sVector Speed;
  sVector Side;
};

static TourqueSimulator *TourqueSim[16];

KObject * __stdcall Init_Effect_Tourque(class GenMaterial *mtrl,
  class GenMesh *mesh,sInt Seed,sInt maxcount,sInt Rate,sInt Flags,
  sF32 SizeF,sF32 SizeS,sF32 Speed,sF32 Damp,sF323 Pos,sF323 Range,sInt Slot)
{
  if(mtrl) mtrl->Release();
  if(mesh) // count tris
  {
    sInt nTris = 0;

    mesh->CalcNormals();

    for(sInt i=0;i<mesh->Face.Count;i++)
    {
      if(!mesh->Face[i].Material)
        continue;

      sInt e = mesh->Face[i].Edge, ee = e, count = 0;

      do
      {
        e = mesh->NextFaceEdge(e);
        count++;
      }
      while(e != ee);

      nTris += count-2;
    }

    maxcount = nTris*3;
  }

  maxcount = sMin<sInt>(maxcount,TourqueSimulator::MaxParticles);
  if(TourqueSim[Slot]==0 || TourqueSim[Slot]->TourqueCount!=maxcount || TourqueSim[Slot]->OldSeed != Seed)
  {
    sSetRndSeed(Seed);
    if(TourqueSim[Slot])
      delete TourqueSim[Slot];

    sREGZONE(TourquePart);
    sREGZONE(TourqueCons);
    sREGZONE(TourqueDraw);

    TourqueSim[Slot] = new TourqueSimulator(maxcount);
    TourqueSim[Slot]->OldSeed = Seed;
  }

  if((Flags & 3) != TourqueSim[Slot]->TourqueMode)
  {
    TourqueSim[Slot]->ClearConstraints();

    if(mesh)
    {
      sInt nPart = 0;

      TourqueSim[Slot]->MeshField(mesh,0.04f * 16.0f);

      // setup particles to match vertices in mesh. also add constraints
      for(sInt i=0;i<mesh->Face.Count;i++)
      {
        if(!mesh->Face[i].Material)
          continue;

        // collect verts
        sInt verts[64];
        sInt e = mesh->Face[i].Edge, ee = e, count = 0;

        do
        {
          verts[count++] = mesh->GetVertId(e);
          e = mesh->NextFaceEdge(e);
        }
        while(e != ee);

        // build tris
        for(sInt j=2;j<count;j++)
        {
          sVector v0 = mesh->VertPos(verts[0]);
          sVector v1 = mesh->VertPos(verts[j-1]);
          sVector v2 = mesh->VertPos(verts[j]);
          //sF32 speed = sFExp(sFGetRnd() * 11.0f - 20.0f);
          sF32 speed = 0.0f;

          TourqueSim[Slot]->ParticlePos[0][nPart+0].Init(v0);
          TourqueSim[Slot]->ParticlePos[0][nPart+1].Init(v1);
          TourqueSim[Slot]->ParticlePos[0][nPart+2].Init(v2);
          v0.AddScale3(mesh->VertNorm(verts[0]),speed);
          v1.AddScale3(mesh->VertNorm(verts[j-1]),speed);
          v2.AddScale3(mesh->VertNorm(verts[j]),speed);
          TourqueSim[Slot]->ParticlePos[1][nPart+0].Init(v0);
          TourqueSim[Slot]->ParticlePos[1][nPart+1].Init(v1);
          TourqueSim[Slot]->ParticlePos[1][nPart+2].Init(v2);
          TourqueSim[Slot]->AddConstraint(nPart+0,nPart+1,v0.Distance(v1));
          TourqueSim[Slot]->AddConstraint(nPart+1,nPart+2,v1.Distance(v2));
          TourqueSim[Slot]->AddConstraint(nPart+2,nPart+0,v2.Distance(v0));
          nPart += 3;
        }
      }

      TourqueSim[Slot]->TourquePCount = nPart;
      TourqueSim[Slot]->TourqueIndex = nPart;
    }
    else
    {
      switch(Flags&3)
      {
      case 1:
        for(sInt i=0;i<maxcount;i++)
          TourqueSim[Slot]->AddConstraint(i,(i+1)%maxcount,0.05f);
        break;
      case 2:
        for(sInt i=0;i<maxcount;i+=16)
          for(sInt j=0;j<16;j++)
            TourqueSim[Slot]->AddConstraint(i+j,i+((j+1)&15),0.05f);
        break;
      }
    }

    TourqueSim[Slot]->TourqueMode = Flags & 3;
  }

  return new GenEffect;
}

void __stdcall Exec_Effect_Tourque(KOp *op,KEnvironment *kenv,
  sInt Seed,sInt maxcount,sInt Rate,sInt Flags,
  sF32 SizeF,sF32 SizeS,sF32 Speed,sF32 Damp,sF323 Pos,sF323 Range,sInt Slot)

{
  GenMaterial *mtrl;
  sInt geo;
  volatile sVertexTSpace3 *vp;
  sVector side;
  sVector av,ar;
  const sF32 minspeed=0.001f;

  mtrl = (GenMaterial *) op->GetLinkCache(0);
  if(!(mtrl && mtrl->Passes.Count>0)) return;

  sBool triMode = op->GetLink(1) != 0;
  if(triMode)
  {
    maxcount = TourqueSim[Slot]->TourquePCount;
    Flags |= 32;
  }

  static sBool init = sFALSE;
  static sInt emitTick = 0;

  av.Init(Pos.x,Pos.y,Pos.z,1);
  ar.Init(Range.x,Range.y,Range.z,1);

  if(TourqueSim[Slot]->LastTime-100>kenv->CurrentTime)
  {
    TourqueSim[Slot]->LastTime = kenv->CurrentTime;
    TourqueSim[Slot]->TourqueIndex = 0;
  }
  sInt slices = 0;
  const sInt timeSliceLen=10;

  if(kenv->CurrentTime - TourqueSim[Slot]->LastTime >= timeSliceLen)
  {
    slices = (kenv->CurrentTime - TourqueSim[Slot]->LastTime) / timeSliceLen;
    TourqueSim[Slot]->LastTime += slices * timeSliceLen;
  }
  slices = sMin(slices,100);

  for(sInt i=0;i<slices;i++)
  {
    if(!(Flags & 32) || TourqueSim[Slot]->TourqueIndex < maxcount)
    {
      for(sInt i=0;i<Rate;i++)
      {
        sVector3 *tp = &TourqueSim[Slot]->ParticlePos[0][TourqueSim[Slot]->TourqueIndex%maxcount];
        sVector v;

        v.InitRnd();
        v.Mul3(ar);
        v.Add3(av);

        tp->x = v.x;
        tp->y = v.y;
        tp->z = v.z;

        TourqueSim[Slot]->ParticlePos[1][TourqueSim[Slot]->TourqueIndex%maxcount] = *tp;
        TourqueSim[Slot]->TourqueIndex++;
      }
    }
    /*for(sInt i=0;i<Rate;i++)
    {
      sVector v;

      v.InitRnd();
      v.Mul3(ar);
      v.Add3(av);
      
      for(sInt j=0;j<3;j++)
      {
        sVector3 *tp = &TourqueSim->ParticlePos[0][TourqueIndex%maxcount];
        tp->x = v.x + ((j == 1) ? 0.10f : 0.0f);
        tp->y = v.y;
        tp->z = v.z + ((j == 2) ? 0.10f : 0.0f);

        TourqueSim->ParticlePos[1][TourqueIndex%maxcount] = *tp;
        TourqueIndex++;
      }
    }*/

    while(TourqueSim[Slot]->TourqueIndex>2*maxcount)
      TourqueSim[Slot]->TourqueIndex-=maxcount;
  }

  maxcount = sMin<sInt>(maxcount,TourqueSimulator::MaxParticles);
  maxcount = sMin(maxcount,TourqueSim[Slot]->TourqueIndex);
  if(!maxcount)
    return;

  TourqueSim[Slot]->ParticleCount = maxcount;
  sF32 tFactor = TourqueSim[Slot]->Advance(kenv->TimeDelta,Speed,Damp);

  sBool cubes = Flags & 0x40;
  sInt used=0;
  kenv->CurrentCam.ModelSpace = kenv->ExecStack.Top();
  if(!cubes)
  {
    geo = sSystem->GeoAdd(sFVF_TSPACE3,sGEO_TRI);
    mtrl->Passes[0].Mtrl->Set(kenv->CurrentCam);
    sSystem->GeoBegin(geo,(triMode?1:3)*maxcount,0,(sF32 **)&vp,0);
  }

  sVector3 *pOld = TourqueSim[Slot]->ParticlePos[0];
  sVector3 *pNew = TourqueSim[Slot]->ParticlePos[1];

  sZONE(TourqueDraw);
  sInt ctr = 2;
  sU32 normCode;

  sMatrix mat;
  mat = kenv->ExecStack.Top();

  for(sInt i=0;i<maxcount;i++,pOld++,pNew++)
  {
    sVector pos,forw,upos;

    // speed
    forw.x = pNew->x - pOld->x;
    forw.y = pNew->y - pOld->y;
    forw.z = pNew->z - pOld->z;

    // position
    pos.x = pOld->x + tFactor * forw.x;
    pos.y = pOld->y + tFactor * forw.y;
    pos.z = pOld->z + tFactor * forw.z;

    if(triMode)
    {
      if(++ctr == 3)
      {
        sVector d1,d2,n;
        d1.x = pNew[1].x - pNew->x;
        d1.y = pNew[1].y - pNew->y;
        d1.z = pNew[1].z - pNew->z;
        d2.x = pNew[2].x - pNew->x;
        d2.y = pNew[2].y - pNew->y;
        d2.z = pNew[2].z - pNew->z;
        n.Cross3(d1,d2);
        n.Unit3();

        normCode = (sFtol(n.x*127.5f+127.5f)<<16)
          + (sFtol(n.y*127.5f+127.5f)<<8)
          + sFtol(n.z*127.5f+127.5f);
        ctr = 0;
      }

      vp->x = pos.x;
      vp->y = pos.y;
      vp->z = pos.z;
      vp->n = normCode;
      vp->s = 0x80ff80;
      vp->c = ~0;
      vp->u = 0.0f;
      vp->v = 0.0f;
      vp++;
    }
    else
    {
  // direction

      sF32 speed = forw.UnitAbs3();
      if(speed<minspeed)
      {
        upos.Init(pos.x,pos.y,pos.z);
        upos.Unit3();
        forw.AddScale3(upos,minspeed-speed);
        forw.Unit3();
      }

  // side

      /*upos.Cross3(forw,tp->Side);
      side.Cross3(upos,forw);
      side.Unit3();
      side.Scale3(0.125f);
      side.Add3(tp->Side);
      side.Unit3();
      tp->Side = side;*/
      side.Init(1,0,0,0);

  // draw

      pos.Rotate34(mat);

      if(cubes)
      {
        CubeBuffer[used++].Init(forw.x*SizeF,forw.y*SizeF,forw.z*SizeF,SizeS);
        CubeBuffer[used++].Init(pos.x,pos.y,pos.z,SizeS);
      }
      else
      {
        forw.Scale3(SizeF);
        side.Scale3(SizeS);

        upos.Add3(pos,forw);
        ((sVertexTSpace3 *) vp)[0].Init(upos.x,upos.y,upos.z,~0,0,0);
        upos.Sub3(pos,forw);
        upos.Add3(side);
        ((sVertexTSpace3 *) vp)[1].Init(upos.x,upos.y,upos.z,~0,0,1);
        upos.Sub3(pos,forw);
        upos.Sub3(side);
        ((sVertexTSpace3 *) vp)[2].Init(upos.x,upos.y,upos.z,~0,1,0);

        vp+=3;
      }
    }
  }
  if(!cubes)
  {
    sSystem->GeoEnd(geo);
    sSystem->GeoDraw(geo);
    sSystem->GeoRem(geo);
  }
  CubeCount = used/2;
}

#endif

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

struct StreamPart
{
  sVector Rand[3];        // randomize vertices of triangle
  sVector Pos;            // x/z deviation on stream
  sF32 Fade;              // fading
};

static StreamPart StreamParts[0x10000];

static sInt StreamPartInit;

KObject * __stdcall Init_Effect_Stream(class GenMaterial *mtrl,StreamPara para)
{
  if(!StreamPartInit)
  {
    StreamPartInit = 1;

    for(sInt i=0;i<0x10000;i++)
    {
      StreamParts[i].Rand[0].InitRnd(); StreamParts[i].Rand[0].w = 0;
      StreamParts[i].Rand[1].InitRnd(); StreamParts[i].Rand[1].w = 0;
      StreamParts[i].Rand[2].InitRnd(); StreamParts[i].Rand[2].w = 0;
      StreamParts[i].Pos.InitRnd();     StreamParts[i].Pos.w = 1;
      StreamParts[i].Fade = sFGetRnd();
    }
  }
  return new GenEffect;
}

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

void __stdcall Exec_Effect_Stream(KOp *op,KEnvironment *kenv,StreamPara para)
{
  GenMaterial *mtrl;
  sInt geo;
  sVertexTSpace3 *vp;
  StreamPart *sp;
  sMatrix mat[4];

  mtrl = (GenMaterial *) op->GetLinkCache(0);
  if(!(mtrl && mtrl->Passes.Count>0)) return;


  sInt maxcount = para.Count;
  sInt maxvert = maxcount*3;

  kenv->CurrentCam.ModelSpace.Init();
  geo = sSystem->GeoAdd(sFVF_TSPACE3,sGEO_TRI);
  mtrl->Passes[0].Mtrl->Set(kenv->CurrentCam);
  sSystem->GeoBegin(geo,maxvert,0,(sF32 **)&vp,0);

  sSetRndSeed(para.Seed);
  sp = StreamParts;

  for(sInt i=0;i<4;i++)
  {
    mat[i].Init();
    mat[i].l = para.Pos[i];
    mat[i].l.w = 1;
    mat[i].i.x = para.Pos[i].w;
    mat[i].j.y = para.Pos[i].w;
    mat[i].k.z = para.Pos[i].w;
  }
  for(sInt i=0;i<maxcount;i++)
  {
    sVector pos,upos;
    sVector b[4];
    sF32 f[4];
    sF32 s;

    pos = sp->Pos;
    pos.w = 1;
    s = sFMod((sp->Fade+para.StuffX),1);
    f[0] =  (1-s)*(1-s)*(1-s);
    f[1] = 3*  s *(1-s)*(1-s);
    f[2] = 3*  s *   s *(1-s);
    f[3] =     s *   s *   s;

    for(sInt j=0;j<3;j++)
    {
      upos = pos;
      upos.AddScale3(sp->Rand[j],para.SizeF);
      b[0].Rotate34(mat[0],upos); 
      b[1].Rotate34(mat[1],upos);
      b[2].Rotate34(mat[2],upos);
      b[3].Rotate34(mat[3],upos);
      upos.   Scale3(b[0],f[0]);
      upos.AddScale3(b[1],f[1]);
      upos.AddScale3(b[2],f[2]);
      upos.AddScale3(b[3],f[3]);
      vp[j].Init(upos.x,upos.y,upos.z,~0,0,0);
    }
    vp+=3;
    sp++;
  }
  sSystem->GeoEnd(geo);
  sSystem->GeoDraw(geo);
  sSystem->GeoRem(geo);
}


/****************************************************************************/
/***                                                                      ***/
/***   Breakpoint 06 Invitation: Scene Spirit Effect                      ***/
/***                                                                      ***/
/****************************************************************************/

#if !sINTRO

KObject * __stdcall Init_Effect_BP06Spirit(class GenMaterial *mtrl,class GenMaterial *mtrl2,sF323 pos,sF32 radius,sF32 corerad,sF32 perlfreq,sF32 perlamp,sF32 perlanim,sU32 coli,sU32 colo,sU32 colc,sInt partcount,sF32 partrad,sF32 partspeed,sF32 partthick,sF32 partanim,sInt segments)
{
  sRelease(mtrl2);
  return MakeEffect(mtrl);
}


void __stdcall   Exec_Effect_BP06Spirit(KOp *op,KEnvironment *kenv,sF323 pos,sF32 radius,sF32 corerad,sF32 perlfreq,sF32 perlamp,sF32 perlanim,sU32 coli,sU32 colo,sU32 colc,sInt partcount,sF32 partrad,sF32 partspeed,sF32 partthick,sF32 partanim,sInt segments)
{
  GenMaterial *mtrl;
  GenMaterial *mtrl2;
  sInt geo;
  sF32 *fp;
  sU16 *ip;
  sMatrix mat;
  sVector p,d;
  static sVector Ring[256];

  // check material

  mtrl = (GenMaterial *) op->GetLinkCache(0);
  if(!(mtrl && mtrl->Passes.Count>0))
    return;
  mtrl2 = (GenMaterial *) op->GetLinkCache(1);
  if(!(mtrl2 && mtrl2->Passes.Count>0))
    mtrl2 = mtrl;
  kenv->CurrentCam.ModelSpace.Init();
  mtrl->Passes[0].Mtrl->Set(kenv->CurrentCam);
  geo = sSystem->GeoAdd(sFVF_TSPACE3,sGEO_TRI);

  // perpare shape

  sInt max = sRange(segments,255,3);
  sSystem->GetTransform(sGT_MODELVIEW,mat);
  for(sInt i=0;i<max;i++)
  {
    sF32 fs = sFSin(i*sPI2F/max);
    sF32 fc = sFCos(i*sPI2F/max);
    Ring[i].x = radius*(mat.i.x*fs + mat.i.y*fc)+pos.x;
    Ring[i].y = radius*(mat.j.x*fs + mat.j.y*fc)+pos.y;
    Ring[i].z = radius*(mat.k.x*fs + mat.k.y*fc)+pos.z;
    p.x = Ring[i].x * perlfreq + perlanim;
    p.y = Ring[i].y * perlfreq + perlanim;
    p.z = Ring[i].z * perlfreq + perlanim;
    sPerlin3D(p,p);
    Ring[i].x += (mat.i.x*p.x + mat.i.y*p.y)*perlamp;
    Ring[i].y += (mat.j.x*p.x + mat.j.y*p.y)*perlamp;
    Ring[i].z += (mat.k.x*p.x + mat.k.y*p.y)*perlamp;
  }

  // paint shape

  sSystem->GeoBegin(geo,max+1,max*3,&fp,(void **)&ip);
  for(sInt i=0;i<max;i++)
  {
    fp[0] = Ring[i].x;
    fp[1] = Ring[i].y;
    fp[2] = Ring[i].z;
    fp[3] = 0;
    fp[4] = 0;
    *((sU32 *)(&fp[5])) = coli;
    fp[6] = 0.5f;
    fp[7] = 0.5f;
    fp += 8;
  }
  fp[0] = pos.x;
  fp[1] = pos.y;
  fp[2] = pos.z;
  fp[3] = 0;
  fp[4] = 0;
  *((sU32 *)(&fp[5])) = coli;
  fp[6] = 0.5f;
  fp[7] = 0.5f;
  for(sInt i=0;i<max;i++)
  {
    *ip++ = i;
    *ip++ = (i+1)%max;
    *ip++ = max;
  }

  sSystem->GeoEnd(geo);
  sSystem->GeoDraw(geo);

  // paint particles

  sRandom rnd;
  sSystem->GeoBegin(geo,partcount*4,partcount*6,&fp,(void **)&ip);
  for(sInt i=0;i<partcount;i++)
  {
    sInt time = (rnd.Int(0x10000)+sInt(partanim*0x1000))&0xffff;

    sInt timernd = time >> 12;
    time = time&0xfff;

    sF32 side = rnd.Float(2)*partthick;
    sInt sidesign = rnd.Int(2)?-1:1;
    sInt pos0 = rnd.Int(max*1024);
    sF32 posf = (pos0&(1024-1))/1024.0f;
    pos0 = (pos0/1024+timernd*7)%max;
    sInt pos1 = (pos0+1)%max;


    p.x = Ring[pos0].x + (Ring[pos1].x-Ring[pos0].x)*posf;
    p.y = Ring[pos0].y + (Ring[pos1].y-Ring[pos0].y)*posf;
    p.z = Ring[pos0].z + (Ring[pos1].z-Ring[pos0].z)*posf;

    d.x = p.x-pos.x;
    d.y = p.y-pos.y;
    d.z = p.z-pos.z;
    d.Unit3();
    p.AddScale3(d,sidesign*(side+time*partspeed/4096));

    sF32 s = (1-time/4096.0f)*partrad;

    fp[0] = p.x + s*(- mat.i.x*s + mat.i.y*s);
    fp[1] = p.y + s*(- mat.j.x*s + mat.j.y*s);
    fp[2] = p.z + s*(- mat.k.x*s + mat.k.y*s);
    fp[3] = 0;  fp[4] = 0;   *((sU32 *)(&fp[5])) = colo;  fp[6] = 0;  fp[7] = 0;
    fp += 8;
    fp[0] = p.x + s*(+ mat.i.x*s + mat.i.y*s);
    fp[1] = p.y + s*(+ mat.j.x*s + mat.j.y*s);
    fp[2] = p.z + s*(+ mat.k.x*s + mat.k.y*s);
    fp[3] = 0;  fp[4] = 0;   *((sU32 *)(&fp[5])) = colo;  fp[6] = 1;  fp[7] = 0;
    fp += 8;
    fp[0] = p.x + s*(+ mat.i.x*s - mat.i.y*s);
    fp[1] = p.y + s*(+ mat.j.x*s - mat.j.y*s);
    fp[2] = p.z + s*(+ mat.k.x*s - mat.k.y*s);
    fp[3] = 0;  fp[4] = 0;   *((sU32 *)(&fp[5])) = colo;  fp[6] = 1;  fp[7] = 1;
    fp += 8;
    fp[0] = p.x + s*(- mat.i.x*s - mat.i.y*s);
    fp[1] = p.y + s*(- mat.j.x*s - mat.j.y*s);
    fp[2] = p.z + s*(- mat.k.x*s - mat.k.y*s);
    fp[3] = 0;  fp[4] = 0;   *((sU32 *)(&fp[5])) = colo;  fp[6] = 0;  fp[7] = 1;
    fp += 8;
  }

  for(sInt i=0;i<partcount;i++)
    sQuad(ip,0+i*4,1+i*4,2+i*4,3+i*4);

  sSystem->GeoEnd(geo);
  sSystem->GeoDraw(geo);

  // paint core

  mtrl2->Passes[0].Mtrl->Set(kenv->CurrentCam);
  sSystem->GeoBegin(geo,4,6,&fp,(void **)&ip);

  fp[0] = corerad*( - mat.i.x + mat.i.y)+pos.x;
  fp[1] = corerad*( - mat.j.x + mat.j.y)+pos.y;
  fp[2] = corerad*( - mat.k.x + mat.k.y)+pos.z;
  fp[3] = 0;  fp[4] = 0;  *((sU32 *)(&fp[5])) = colc;  fp[6] = 0.0f;  fp[7] = 0.0f;
  fp += 8;
  fp[0] = corerad*( + mat.i.x + mat.i.y)+pos.x;
  fp[1] = corerad*( + mat.j.x + mat.j.y)+pos.y;
  fp[2] = corerad*( + mat.k.x + mat.k.y)+pos.z;
  fp[3] = 0;  fp[4] = 0;  *((sU32 *)(&fp[5])) = colc;  fp[6] = 1.0f;  fp[7] = 0.0f;
  fp += 8;
  fp[0] = corerad*( + mat.i.x - mat.i.y)+pos.x;
  fp[1] = corerad*( + mat.j.x - mat.j.y)+pos.y;
  fp[2] = corerad*( + mat.k.x - mat.k.y)+pos.z;
  fp[3] = 0;  fp[4] = 0;  *((sU32 *)(&fp[5])) = colc;  fp[6] = 1.0f;  fp[7] = 1.0f;
  fp += 8;
  fp[0] = corerad*( - mat.i.x - mat.i.y)+pos.x;
  fp[1] = corerad*( - mat.j.x - mat.j.y)+pos.y;
  fp[2] = corerad*( - mat.k.x - mat.k.y)+pos.z;
  fp[3] = 0;  fp[4] = 0;  *((sU32 *)(&fp[5])) = colc;  fp[6] = 0.0f;  fp[7] = 1.0f;
  fp += 8;

  sQuad(ip,0,1,2,3);

  sSystem->GeoEnd(geo);
  sSystem->GeoDraw(geo);

  // done

  sSystem->GeoRem(geo);
}

#endif

/****************************************************************************/
/***                                                                      ***/
/***  Breakpoint 06 Invitation: Dschungelgelöt                            ***/
/***                                                                      ***/
/****************************************************************************/

KObject * __stdcall Init_Effect_BP06Jungle(GenMaterial *mtrl,sInt segments,sInt slices,sF32 thickness,sF32 thickscale,sF32 length,sF32 lenscale,sF32 sangle,sF32 carfreq,sF32 caramp,sF32 modfreq,sF32 modamp,sF32 modphase)
{
  return MakeEffect(mtrl);
}

void __stdcall Exec_Effect_BP06Jungle(KOp *op,KEnvironment *kenv,sInt segments,sInt slices,sF32 thickness,sF32 thickscale,sF32 length,sF32 lenscale,sF32 sangle,sF32 carfreq,sF32 caramp,sF32 modfreq,sF32 modamp,sF32 modphase)
{
  GenMaterial *mtrl;
  volatile sVertexTSpace3 *vp;
  sU16 *ip;
  sInt geo;

  // check material

  mtrl = (GenMaterial *) op->GetLinkCache(0);
  if(!(mtrl && mtrl->Passes.Count>0))
    return;

  kenv->CurrentCam.ModelSpace = kenv->ExecStack.Top();
  mtrl->Passes[0].Mtrl->Set(kenv->CurrentCam);

  geo = sSystem->GeoAdd(sFVF_TSPACE3,sGEO_TRI|sGEO_DYNAMIC);

  // paint the effect

  sSystem->GeoBegin(geo,(segments+1)*(slices+1),segments*slices*6,(sF32 **) &vp,(void **) &ip);

  sF32 angle;
  sF32 posx,posy;

  angle = sangle;
  posx = posy = 0.0f;

  length /= segments;
  lenscale = sFPow(lenscale,1.0f / segments);
  thickscale = sFPow(thickscale,1.0f / segments);
  caramp /= segments;
  angle = sangle;

  for(sInt i=0;i<=segments;i++)
  {
    // calculate angles
    sF32 ca,sa,v;

    v = 1.0f * i / segments;
    angle += caramp * sFCos(sPI2F * v * (carfreq + modamp * sFSin(sPI2F * (v * modfreq + modphase))));
    sFSinCos(angle*sPI2F,sa,ca);

    // build the segments
    for(sInt j=0;j<=slices;j++)
    {
      sF32 u = 1.0f * j / slices;

      vp->x = posx + (u - 0.5f) * ca * thickness;
      vp->y = posy - (u - 0.5f) * sa * thickness;
      vp->z = 0.0f;
      vp->n = 0x808000;
      vp->s = 0x80ff80;
      vp->c = 0xffffffff;
      vp->u = u;
      vp->v = 1.0f - v;
      vp++;
    }

    // update angle and position
    posx += sa * length;
    posy += ca * length;
    length *= lenscale;
    thickness *= thickscale;
  }

  sInt slice1 = slices + 1;

  for(sInt i=0;i<segments;i++)
    for(sInt j=0;j<slices;j++)
      sQuad(ip,i*slice1+j,i*slice1+j+1,(i+1)*slice1+j+1,(i+1)*slice1+j);

  sSystem->GeoEnd(geo);
  sSystem->GeoDraw(geo);

  // done

  sSystem->GeoRem(geo);
}

/****************************************************************************/
/***                                                                      ***/
/***  Gridkram                                                            ***/
/***                                                                      ***/
/****************************************************************************/

KObject * __stdcall Init_Effect_GridStuff(GenMaterial *mtrl,sInt xRes,sInt yRes,sU32 col1,sU32 col2)
{
  return MakeEffect(mtrl);
}

static sU32 getColor(sF32 fade,sU32 col1,sU32 col2)
{
  sInt f = sRange<sInt>(fade*255,255,0);
  sInt col1rb = col1 & 0xff00ff;
  sInt col1ag = (col1 >> 8) & 0xff00ff;
  sInt col2rb = col2 & 0xff00ff;
  sInt col2ag = (col2 >> 8) & 0xff00ff;

  sInt rb = (col1rb + ((f*(col2rb - col1rb))>>8)) & 0xff00ff;
  sInt ag = (col1ag + ((f*(col2ag - col1ag))>>8)) & 0xff00ff;

  return (ag<<8) | rb;
}

void __stdcall Exec_Effect_GridStuff(KOp *op,KEnvironment *kenv,sInt xRes,sInt yRes,sU32 col1,sU32 col2)
{
  GenMaterial *mtrl;
  volatile sVertexTSpace3 *vp;
  sInt geo;

  // check material

  mtrl = (GenMaterial *) op->GetLinkCache(0);
  if(!(mtrl && mtrl->Passes.Count>0))
    return;

  kenv->CurrentCam.ModelSpace = kenv->ExecStack.Top();
  mtrl->Passes[0].Mtrl->Set(kenv->CurrentCam);

  geo = sSystem->GeoAdd(sFVF_TSPACE3,sGEO_QUAD|sGEO_DYNAMIC);

  // calc random heightfield
  sF32 *heights = new sF32[xRes*yRes];
  sSetRndSeed(0x12345);

  sF32 time = kenv->Var[KV_TIME].x;
  sInt timeInt = sInt(time);
  sF32 timeFrac = time - timeInt + (timeInt<0);
  timeInt &= 0xff;

  // random points interpolated with catmull-rom
  for(sInt i=0;i<xRes*yRes;i++)
  {
    sInt prePerm = sPerlinPermute[sPerlinPermute[i&0xff]+(i>>8)];

    // 4 base points
    sF32 basev[4];
    for(sInt j=0;j<4;j++)
      basev[j] = sPerlinPermute[(prePerm+timeInt+j-1) & 0xff] / 255.0f;

    // interpolate (catmull-rom)
    heights[i] =
      (((0.5f * (basev[3] - basev[0]) + 1.5f*(basev[1]-basev[2])) * timeFrac +
      basev[0] - 2.5f * basev[1] + 2.0f * basev[2] - 0.5f * basev[3]) * timeFrac +
      0.5f * (basev[2] - basev[0])) * timeFrac + basev[1];

    //heights[i] = sFGetRnd();
  }

  // all independent quads
  sSystem->GeoBegin(geo,(xRes*yRes+(xRes-1)*yRes+xRes*(yRes-1))*4,0,(sF32 **) &vp,0);

  const sF32 yScale = 5.0f;

  // main quads
  for(sInt y=0;y<yRes;y++)
  {
    for(sInt x=0;x<xRes;x++)
    {
      sF32 h = heights[y*xRes+x];
      sU32 c = getColor(h*0.5f+0.5f,col1,col2);

      for(sInt j=0;j<4;j++)
      {
        sInt k = j ^ (j>>1);

        vp->x = x + (k&1);
        vp->y = h * yScale;
        vp->z = y + 1 - (k>>1);
        vp->n = 0x80ff80;
        vp->s = 0x8080ff;
        vp->c = c;
        vp->u = x + (k&1);
        vp->v = y + (k>>1);
        vp++;
      }
    }
  }

  // horizontal connectors
  for(sInt y=0;y<yRes;y++)
  {
    for(sInt x=0;x<xRes-1;x++)
    {
      sF32 h1 = heights[y*xRes+x];
      sF32 h2 = heights[y*xRes+x+1];
      sU32 c1 = getColor(h1,col1,col2);
      sU32 c2 = getColor(h2,col1,col2);
      sU32 n = h1 >= h2 ? 0xff8080 : 0x008080;

      for(sInt j=0;j<4;j++)
      {
        sInt k = j ^ (j>>1);

        vp->x = x + 1;
        vp->y = ((k&1) ? h2 : h1) * yScale;
        vp->z = y + 1 - (k>>1);
        vp->n = n;
        vp->s = 0x8080ff;
        vp->c = (k&1) ? c2 : c1;
        vp->u = x + 1 + (k&1);
        vp->v = y + (k>>1);
        vp++;
      }
    }
  }

  // vertical connectors
  for(sInt y=0;y<yRes-1;y++)
  {
    for(sInt x=0;x<xRes;x++)
    {
      sF32 h1 = heights[y*xRes+x];
      sF32 h2 = heights[y*xRes+x+xRes];
      sU32 c1 = getColor(h1,col1,col2);
      sU32 c2 = getColor(h2,col1,col2);
      sU32 n = h1 >= h2 ? 0x8080ff : 0x808000;

      for(sInt j=0;j<4;j++)
      {
        sInt k = j ^ (j>>1);

        vp->x = x + 1 - (k&1);
        vp->y = ((k&2) ? h2 : h1) * yScale;
        vp->z = y + 1;
        vp->n = n;
        vp->s = 0xff8080;
        vp->c = (k&2) ? c2 : c1;
        vp->u = x + (k&1);
        vp->v = y + 1 + (k>>1);
        vp++;
      }
    }
  }

  sSystem->GeoEnd(geo);
  sSystem->GeoDraw(geo);

  sSystem->GeoRem(geo);
  delete[] heights;
}

/****************************************************************************/
/***                                                                      ***/
/***  Image Jukebox                                                       ***/
/***                                                                      ***/
/****************************************************************************/

GenImageList::GenImageList()
{
  ClassId = KC_IMGLIST;
  Images.Init();
}

GenImageList::~GenImageList()
{
  Clear();
  Images.Exit();
}

void GenImageList::Copy(KObject *o)
{
  sVERIFY(o->ClassId == KC_IMGLIST);

  Clear();
  Append(*((GenImageList *) o));
}

void GenImageList::Clear()
{
  for(sInt i=0;i<Images.Count;i++)
    sSystem->RemTexture(Images[i].TexHandle);
}

void GenImageList::AddImage(sInt handle,sInt xRes,sInt yRes)
{
  if(handle != sINVALID)
  {
    sSystem->AddRefTexture(handle);

    Image* img = Images.Add();
    img->TexHandle = handle;
    img->XRes = xRes;
    img->YRes = yRes;
  }
}

void GenImageList::Append(const GenImageList& other)
{
  Images.AtLeast(Images.Count + other.Images.Count);
  for(sInt i=0;i<other.Images.Count;i++)
  {
    Image *img = Images.Add();
    *img = other.Images[i];
    sSystem->AddRefTexture(img->TexHandle);
  }
}

sInt GenImageList::GetImage(sInt index,sInt *xRes,sInt *yRes) const
{
  if(index<0 || index>=Images.Count)
    return sINVALID;

  if(xRes) *xRes = Images[index].XRes;
  if(yRes) *yRes = Images[index].YRes;

  return Images[index].TexHandle;
}

GenImageList * __stdcall Init_Misc_ImageList(KOp *op)
{
  sInt xRes = *op->GetEditPtrU(0);
  sInt yRes = *op->GetEditPtrU(1);

  GenImageList *result = new GenImageList;
  for(sInt i=0;i<op->GetInputCount();i++)
  {
    KObject *in = op->GetInput(i)->Cache;

    if(in->ClassId == KC_IMGLIST)
      result->Append(*((GenImageList *) in));
    else if(in->ClassId == KC_BITMAP)
    {
      GenBitmap *bmp = (GenBitmap *) in;
      if(bmp->Texture == sINVALID)
        bmp->MakeTexture(bmp->Format);

      result->AddImage(bmp->Texture,xRes,yRes);
    }
    else
    {
      delete result;
      result = 0;
      break;
    }
  }

  if(result)
  {
    // give up refs to inputs if successful
    for(sInt i=0;i<op->GetInputCount();i++)
      op->GetInput(i)->Cache->Release();
  }

  return result;
}

class sMaterialImageJukebox : public sMaterial
{
  sInt Setup;

public:
  sInt Tex;
  sF32 Whiten;
  sF32 Amplify;
  sF32 Fade;

  sMaterialImageJukebox()
  {
    sU32 states[256],*st=states;

    static const sU32 vShader[] = {
      0xfffe0101,                                       // vs.1.1
      0x0000001f, 0x80000000, 0x900f0000,               // dcl_position v0
      0x0000001f, 0x80000005, 0x900f0001,               // dcl_texcoord v1
      0x00000014, 0xc00f0000, 0x90e40000, 0xa0e40000,   // m4x4 oPos,v0,c0
      0x00000001, 0xe00f0000, 0x90e40001,               // mov oT0,v1
      0x0000ffff,                                       // end
    };

    static const sU32 pShader[] = {
      0xffff0200,                                       // ps.2.0
      0x0200001f, 0x80000000, 0xb00f0000,               // dcl t0
      0x0200001f, 0x90000000, 0xa00f0800,               // dcl_2d s0
      0x03000042, 0x800f0000, 0xb0e40000, 0xa0e40800,   // texld r0,t0,s0
      0x04000012, 0x800f0001, 0xa0550000, 0x80e40000,
        0x80ff0000,                                     // lrp r1,c0.y,r0,r0.w
      0x03000005, 0x80070000, 0x80e40001, 0xa0aa0000,   // mul r0.rgb,r1,c0.z
      0x0300000a, 0x80070000, 0x80e40000, 0x80ff0000,   // min r0.rgb,r0,r0.a
      0x03000005, 0x800f0000, 0x80e40000, 0xa0ff0000,   // mul r0,r0,r0.a
      0x02000001, 0x800f0800, 0x80e40000,               // mov oC0,r0
      0x0000ffff,                                       // end
    };

    // render states
    *st++ = sD3DRS_ALPHATESTENABLE;                 *st++ = 0;
    *st++ = sD3DRS_ZENABLE;                         *st++ = sD3DZB_TRUE;
    *st++ = sD3DRS_ZWRITEENABLE;                    *st++ = 0;
    *st++ = sD3DRS_ZFUNC;                           *st++ = sD3DCMP_LESS;
    *st++ = sD3DRS_CULLMODE;                        *st++ = sD3DCULL_NONE;
    *st++ = sD3DRS_COLORWRITEENABLE;                *st++ = 15;
    *st++ = sD3DRS_SLOPESCALEDEPTHBIAS;             *st++ = 0;
    *st++ = sD3DRS_DEPTHBIAS;                       *st++ = 0;
    *st++ = sD3DRS_FOGENABLE;                       *st++ = 0;
    *st++ = sD3DRS_STENCILENABLE;                   *st++ = 0;
    *st++ = sD3DRS_ALPHABLENDENABLE;                *st++ = 1;
    *st++ = sD3DRS_SRCBLEND;                        *st++ = sD3DBLEND_ONE;
    *st++ = sD3DRS_DESTBLEND;                       *st++ = sD3DBLEND_INVSRCALPHA;
    *st++ = sD3DRS_BLENDOP;                         *st++ = sD3DBLENDOP_ADD;

    // sampler setup
    *st++ = sD3DSAMP_0|sD3DSAMP_MAGFILTER;          *st++ = sD3DTEXF_LINEAR;
    *st++ = sD3DSAMP_0|sD3DSAMP_MINFILTER;          *st++ = sD3DTEXF_LINEAR;
    *st++ = sD3DSAMP_0|sD3DSAMP_MIPFILTER;          *st++ = sD3DTEXF_LINEAR;
    *st++ = sD3DSAMP_0|sD3DSAMP_ADDRESSU;           *st++ = sD3DTADDRESS_CLAMP;
    *st++ = sD3DSAMP_0|sD3DSAMP_ADDRESSV;           *st++ = sD3DTADDRESS_CLAMP;

    // terminator
    *st++ = ~0U;                                    *st++ = ~0U;

    // create setup
    Setup = sSystem->MtrlAddSetup(states,vShader,pShader);

    Whiten = 0.0f;
    Amplify = 1.0f;
    Fade = 1.0f;
  }

  ~sMaterialImageJukebox()
  {
    if(Setup != sINVALID)
      sSystem->MtrlRemSetup(Setup);
  }

  void Set(const sMaterialEnv &env)
  {
    sMaterialInstance inst;
    sMatrix mat;
    sVector pc[2];

    inst.NumTextures = 1;
    inst.Textures[0] = Tex;
    inst.NumVSConstants = 4;
    inst.VSConstants = &mat.i;
    inst.NumPSConstants = 2;
    inst.PSConstants = pc;

    // change to proper ortho projection here
    mat.Mul4(env.ModelSpace,sSystem->LastViewProject);
    mat.Trans4();

    pc[0].Init(1.0f,1.0f-Whiten,Amplify,Fade);
    pc[1].Init(0.0f,0.0f,0.0f,0.0f);

    sSystem->MtrlSetSetup(Setup);
    sSystem->MtrlSetInstance(inst);
  }
};

KObject * __stdcall Init_Effect_ImageJukebox(GenImageList *list,sInt pass,sInt index,sF32 whiten,sF32 amplify,sF32 fade)
{
  GenMaterial *mtrl = new GenMaterial;
  mtrl->AddPass(new sMaterialImageJukebox,ENGU_OTHER,MPP_STATIC,pass);

  GenEffect *fx = new GenEffect;
  fx->Material = mtrl;
  fx->Pass = pass;
  fx->Usage = ENGU_OTHER;

  if(list)
    list->Release();

  return fx;
}

void __stdcall Exec_Effect_ImageJukebox(KOp *op,KEnvironment *kenv,sInt pass,sInt index,sF32 whiten,sF32 amplify,sF32 fade)
{
  GenImageList *list = (GenImageList *) op->GetLinkCache(0);
  if(!list || list->ClassId != KC_IMGLIST)
    return;

  // prepare texture/material
  GenEffect *fx = (GenEffect *) op->Cache;
  sMaterialImageJukebox *mtrl = (sMaterialImageJukebox *) fx->Material->Passes[0].Mtrl;
  
  sInt xRes,yRes;

  mtrl->Tex = list->GetImage(index,&xRes,&yRes);
  mtrl->Whiten = whiten;
  mtrl->Amplify = amplify;
  mtrl->Fade = fade;

  // set material
  kenv->CurrentCam.ModelSpace = kenv->ExecStack.Top();
  mtrl->Set(kenv->CurrentCam);

  // calc dimensions
  sF32 xDim = xRes / 256.0f;
  sF32 yDim = yRes / 256.0f;

  // paint the quad
  sVertexDouble *vert;

  sInt geo = sSystem->GeoAdd(sFVF_DOUBLE,sGEO_QUAD);
  
  sSystem->GeoBegin(geo,4,0,(sF32 **) &vert,0);
  vert[0].Init(-xDim, yDim,0,~0U,0,0);
  vert[1].Init( xDim, yDim,0,~0U,1,0);
  vert[2].Init( xDim,-yDim,0,~0U,1,1);
  vert[3].Init(-xDim,-yDim,0,~0U,0,1);
  sSystem->GeoEnd(geo);
  sSystem->GeoDraw(geo);

  sSystem->GeoRem(geo);
}