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

#include "_start.hpp"
#include "genscene.hpp"
#include "genmesh.hpp"
#include "genminmesh.hpp"
#include "genoverlay.hpp"
#include "genmaterial.hpp"
#include "geneffect.hpp"
#include "genblobspline.hpp"
#include "kkriegergame.hpp"
#include "engine.hpp"
#include <xmmintrin.h>

#if !sPLAYER
#include "winview.hpp"
#endif

#if sPROFILE
#include "_util.hpp"

sMAKEZONE(ExecWalk    ,"ExecWalk "  ,0xff8000d0);
sMAKEZONE(PaintScene  ,"PaintScene" ,0xffb040ff);
sMAKEZONE(ExecXForm   ,"ExecXForm"  ,0xff593d33);
sMAKEZONE(InstanceMat ,"InstanceMat",0xffee2121);
#endif

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

#if !sPLAYER
KOp *ShowPortalOp;
sVector ShowPortalCube[8];
sBool ShowPortalOpProcessed;

sBool SceneWireframe = sFALSE;
sInt SceneWireFlags = 0;
sU32 SceneWireMask = 0;
#endif

sInt GenScenePasses = ~0;
static sInt RenderPassAdjust = 0;

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

#define SCRIPTVERIFY(x) {if(!(x)) return 0;}

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

GenScene::GenScene()
{
  ClassId = KC_SCENE;
  Childs.Init();
  DrawMesh = 0;
#if sLINK_KKRIEGER
  CollMesh = 0;
#endif
  Effect = 0;
  sSetMem(SRT,0,sizeof(SRT));
  SRT[0] = 1;
  SRT[1] = 1;
  SRT[2] = 1;
  Count = 0;
  Next = 0;
  IsSector = sFALSE;
}

GenScene::~GenScene()
{
  sInt i;

  for(i=0;i<Childs.Count;i++)
    Childs[i]->Release();
  Childs.Exit();

  if(DrawMesh) DrawMesh->Release();
#if sLINK_KKRIEGER
  if(CollMesh) CollMesh->Release();
#endif
  if(Effect) Effect->Release();
}

void GenScene::Copy(KObject *o)
{
  sInt i;
  GenScene *scene;

  sVERIFY(o->ClassId==KC_SCENE);
  scene = (GenScene *) o;

  Childs.Copy(scene->Childs);
  for(i=0;i<Childs.Count;i++)
    Childs[i]->AddRef();

  DrawMesh = scene->DrawMesh;
  if(DrawMesh) DrawMesh->AddRef();

#if sLINK_KKRIEGER
  CollMesh = scene->CollMesh;
  if(CollMesh) CollMesh->AddRef();
#endif

  Effect = scene->Effect;
  if(Effect) Effect->AddRef();

  sCopyMem(SRT,scene->SRT,sizeof(SRT));
  Count = scene->Count;
  IsSector = scene->IsSector;
  Next = 0;
}

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

GenScene *MakeScene(KObject *in)
{
  GenScene *scene;
  EngMesh *mesh;

  scene = 0;
  if(in)
  {
    if(in->ClassId==KC_SCENE)
    {
      scene = (GenScene *)in;
    }
#if sLINK_FATMESH
    else if(in->ClassId==KC_MESH)
    {
      GenMesh *inMesh;
      scene = new GenScene;
      inMesh = (GenMesh *)in;
      if(inMesh)
      {
        inMesh->Compact();

#if !sPLAYER
        if(SceneWireframe)
        {
          inMesh->PrepareWire(SceneWireFlags,SceneWireMask);
          mesh = inMesh->WireMesh;
        }
        else
        {
          inMesh->UnPrepareWire();
          inMesh->Prepare();
          mesh = inMesh->PreparedMesh;
        }
#else
        inMesh->Prepare();
        mesh = inMesh->PreparedMesh;
#endif

        if(mesh)
        {
          scene->DrawMesh = mesh;
          scene->DrawMesh->AddRef();

#if sPLAYER
          mesh->Preload();
#endif
        }

#if sLINK_KKRIEGER
        scene->CollMesh = new KKriegerMesh(inMesh);
#endif
        inMesh->Release();
      }
    }
#endif
    else if(in->ClassId==KC_EFFECT)
    {
      scene = new GenScene;
      scene->Effect = (GenEffect *)in;
    }
#if sLINK_MINMESH
    else if(in->ClassId==KC_MINMESH)
    {
      GenMinMesh *minMesh;
      scene = new GenScene;
      minMesh = (GenMinMesh *)in;
      if(minMesh)
      {

#if !sPLAYER
        if(SceneWireframe)
        {
          minMesh->PrepareWire(SceneWireFlags,SceneWireMask);
          mesh = minMesh->WireMesh;
        }
        else
        {
          minMesh->UnPrepareWire();
          minMesh->Prepare();
          mesh = minMesh->PreparedMesh;
        }
#else
        minMesh->Prepare();
        mesh = minMesh->PreparedMesh;
#endif

        if(mesh)
        {
          scene->DrawMesh = mesh;
          scene->DrawMesh->AddRef();

#if sPLAYER
          mesh->Preload();
#endif
        }

        minMesh->Release();
      }
    }
#endif
  }

  return scene;
}

/****************************************************************************/
/***                                                                      ***/
/***   The Operators                                                      ***/
/***                                                                      ***/
/****************************************************************************/

void ExecSceneInput(KOp *parent,KEnvironment *kenv,sInt i)
{
  KOp *op;
  GenScene *scene;
  EngMesh *drawMesh;
  sInt classId;

  op = parent->GetInput(i);

  classId = KC_NULL;
#if sPLAYER
  if(op->CacheFreed)
    classId = op->Result;
  else
#endif
  if(op->Cache)
    classId = op->Cache->ClassId;

  if(classId == KC_MESH || classId == KC_MINMESH || classId == KC_EFFECT)
  {
    scene = (GenScene *)parent->Cache;
    if(scene->Childs.Count)
    {
      sVERIFY(i < scene->Childs.Count);
      drawMesh = scene->Childs[i]->DrawMesh;
    }
    else
      drawMesh = scene->DrawMesh;

    if(classId == KC_MESH || classId==KC_MINMESH)
    {
#if sLINK_KKRIEGER
      // this monster culling HACK should be removed.
      if(kenv->CurrentEvent && kenv->CurrentEvent->CullDist>0)
      {
        sVector v;
        v.Sub3(kenv->ExecStack.Top().l,kenv->GameCam.CameraSpace.l);
        if(kenv->CurrentEvent->CullDist*kenv->CurrentEvent->CullDist<v.Dot3(v))
          return;
      }
#endif
      Engine->AddPaintJob(drawMesh,kenv->ExecStack.Top(),kenv->Var[KV_TIME].x,RenderPassAdjust);
    }
    else // effect
      Engine->AddPaintJob(op,kenv->ExecStack.Top(),kenv,RenderPassAdjust);
  }
  else
  {
    op->Exec(kenv);
  }
}

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

void ExecSceneInputs(KOp *parent,KEnvironment *kenv,sF32 *srt)
{
  sInt i,max;
  GenScene *scene;
  sMatrix mat;

  scene = (GenScene *) parent->Cache;

  if(srt)
  {
    mat.InitSRT(srt);
    kenv->ExecStack.PushMul(mat);
  }
  
  max = parent->GetInputCount();
  for(i=0;i<max;i++)
    ExecSceneInput(parent,kenv,i);

  if(srt)
    kenv->ExecStack.Pop();
}

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

GenScene * __stdcall Init_Scene_Scene(GenMesh *mesh,sF323 s,sF323 r,sF323 t,sBool lightmap)
{
  GenScene *scene;

  if(mesh==0)
    return new GenScene;

  scene = MakeScene(mesh);
  if(scene)
    sCopyMem(scene->SRT,&s.x,9*4);

  return scene;
}

void __stdcall Exec_Scene_Scene(KOp *op,KEnvironment *kenv,sF323 s,sF323 r,sF323 t,sBool lightmap)
{
  ExecSceneInputs(op,kenv,&s.x);
}

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

GenScene * __stdcall Init_Scene_Add(sInt count,GenScene *s0,...)
{
  GenScene *scene,*add;
  sInt i;

  scene = new GenScene;
  for(i=0;i<count;i++)
  {
    add = MakeScene((&s0)[i]);
    if(add)
      *scene->Childs.Add() = add;
  }

  return scene;
}

void __stdcall Exec_Scene_Add(KOp *op,KEnvironment *kenv)
{
  ExecSceneInputs(op,kenv,0);
}

GenScene * __stdcall Init_Scene_Select(sInt select,sInt count,GenScene *s0,...)
{
  GenScene *scene,*add;
  sInt i;

  scene = new GenScene;
  for(i=0;i<count;i++)
  {
    add = MakeScene((&s0)[i]);
    if(add)
      *scene->Childs.Add() = add;
  }

  return scene;
}

void __stdcall Exec_Scene_Select(KOp *op,KEnvironment *kenv)
{
  sInt max = op->GetInputCount();
  if(max>0)
    ExecSceneInput(op,kenv,sFtol(*op->GetAnimPtrF(0))%max);
}

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

GenScene * __stdcall Init_Scene_Multiply(GenScene *add,sF323 s,sF323 r,sF323 t,sInt count)
{
  GenScene *scene;

  add = MakeScene(add);
  if(!add) return 0;

  scene = new GenScene;
  *scene->Childs.Add() = add;
  scene->Count = count;
  sCopyMem(scene->SRT,&s.x,9*4);

  return scene;
}

void __stdcall Exec_Scene_Multiply(KOp *op,KEnvironment *kenv,sF323 s,sF323 r,sF323 t,sInt count)
{
  sMatrix mat,mat2;
  sInt i;
  sVector save;

  kenv->ExecStack.Dup();
  mat.InitSRT(&s.x);
  save = kenv->Var[KV_SELECT];
  for(i=0;i<count;i++)
  {
    kenv->Var[KV_SELECT].Init(i,i,i,i);
    ExecSceneInputs(op,kenv,0);
    mat2.MulA(mat,kenv->ExecStack.Top());
    kenv->ExecStack.Pop();
    kenv->ExecStack.Push(mat2);
  }
  kenv->ExecStack.Pop();
  kenv->Var[KV_SELECT] = save;
}

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

GenScene * __stdcall Init_Scene_Transform(GenScene *add,sF323 s,sF323 r,sF323 t)
{
  return Init_Scene_Multiply(add,s,r,t,0);
}

void __stdcall Exec_Scene_Transform(KOp *op,KEnvironment *kenv,sF323 s,sF323 r,sF323 t)
{
  ExecSceneInputs(op,kenv,&s.x);
}

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

GenScene * __stdcall Init_Scene_Light(sF323 r,sF323 t,sU32 flags,sF32 zoom,sU32 color,sF32 amplify,sF32 range)
{
  return new GenScene;
}

void __stdcall Exec_Scene_Light(KOp *op,KEnvironment *kenv,sF323 r,sF323 t,sU32 flags,sF32 zoom,sU32 color,sF32 amplify,sF32 range)
{
  //KSceneJob *job;
  sF32 srt[9];
  sMatrix mat;//,mat2;

//  sVERIFY(op->GetInputCount()==0);

  srt[0] = 1;
  srt[1] = 1;
  srt[2] = 1;
  srt[3] = r.x;
  srt[4] = r.y;
  srt[5] = r.z;
  srt[6] = t.x;
  srt[7] = t.y;
  srt[8] = t.z;
  mat.InitSRT(srt);
  kenv->ExecStack.PushMul(mat);
  //mat2.Mul4(mat,kenv->ExecMatrix);

#if !sPLAYER
  if(ShowPortalOp == op)
  {
    ShowPortalCube[0] = kenv->ExecStack.Top().l;
    ShowPortalOpProcessed = sTRUE;
  }
#endif

  EngLight light;
  light.Type = (flags & 4) ? sLT_DIRECTIONAL : sLT_POINT;
  light.Position = kenv->ExecStack.Top().l;
  light.Direction = kenv->ExecStack.Top().k;
  if(!range && (flags & 4))
    light.Position.Scale4(light.Direction,1e+6f);
  light.Flags = flags;
  // ignore zoom
  light.Color = color;
  light.Amplify = amplify;
  light.Range = range ? range : 1e+15f;
  light.Event = kenv->CurrentEvent;
  light.Id = (sInt) op;

  Engine->AddLightJob(light);

  kenv->ExecStack.Pop();
}


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

GenScene * __stdcall Init_Scene_Camera(sF323 s,sF323 r,sF323 t)
{
  GenScene *scene;

  scene = new GenScene;
  sCopyMem(scene->SRT,&s.x,9*4);

  return scene;
}

void __stdcall Exec_Scene_Camera(KOp *op,KEnvironment *kenv,sF323 s,sF323 r,sF323 t)
{
  sMatrix mat;
  
#if !sPLAYER
  if(!GenOverlayManager->LinkEdit)
  {
    mat.InitSRT(&s.x);
    kenv->GameCam.CameraSpace.MulA(mat,kenv->ExecStack.Top());
  }
#else
  {
    mat.InitSRT(&s.x);
    kenv->GameCam.CameraSpace.MulA(mat,kenv->ExecStack.Top());
  }
#endif
}

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

GenScene * __stdcall Init_Scene_Limb(GenScene *scene0,GenScene *scene1,sF323 pos,sF323 dir,sF32 l0,sF32 l1,sU32 flags)
{
  GenScene *scene;
  GenScene *add;

  scene = new GenScene();

  add =  MakeScene(scene0);
  if(add) *scene->Childs.Add() = add;
  add =  MakeScene(scene1);
  if(add) *scene->Childs.Add() = add;

  return scene;
}

void __stdcall Exec_Scene_Limb(KOp *op,KEnvironment *kenv,sF323 pos,sF323 dirv,sF32 a,sF32 b,sU32 flags)
{
  sMatrix mat0,mat1,var;
  sF32 c,s,t;
  sVector i,j,k;
  
//  flags &= ~2; // relative mode ist kaputt
  if(op->GetInputCount()==2)
  {
    const sMatrix &matrix = kenv->ExecStack.Top();
    if(!(flags&2))
    {
      pos.x -= matrix.l.x;
      pos.y -= matrix.l.y;
      pos.z -= matrix.l.z;
    }

    j.Init(-pos.x,-pos.y,-pos.z,0);    // t-direction
    i.Init(dirv.x,dirv.y,dirv.z,0);
    if(!(flags&2))
      i.Rotate3(matrix);
    k.Cross4(i,j);                  // cross vector
    i.Cross4(j,k);                  // s-direction
    c = j.Abs3();                   // length
    i.Unit3();
    j.Unit3();
    k.Unit3();

    t = (c*c+a*a-b*b)/(2*c);       // b²=c²+a²-2ca cos(ß);  t=a cos(ß);
    /*if(a<=t)
      s=0;
    else
      s = sFSqrt(a*a-t*t);*/
    s = (a-t) * (a+t);
    if(s <= 0.0f)
      s = 0.0f;
    else
      s = sFSqrt(s);

    mat0.k = k;
    mat0.j.Scale4(j,t);
    mat0.j.AddScale3(i,s);
    mat0.j.Unit3();
    mat0.i.Cross4(mat0.j,mat0.k);
    mat0.l.Init4(0,0,0,1);


    if(a<=t && !(flags&1))
    {
      mat1 = mat0;
      mat1.l.AddScale3(j,-a-b);
    }
    else
    {
      mat1.k = k;
      mat1.j.Scale4(j,c-t);
      mat1.j.AddScale3(i,-s);
      mat1.j.Unit3();
      mat1.i.Cross4(mat1.j,mat1.k);
      mat1.l.Init4(pos.x,pos.y,pos.z,1);
    }

    if(flags&2)
      kenv->ExecStack.PushMul(mat0);
    else
    {
      kenv->ExecStack.Push(mat0);
      kenv->ExecStack.Top().l.Add3(matrix.l);
    }
    kenv->SetMatrix(mat1,var);
    ExecSceneInput(op,kenv,0);
    //op->ExecInput(kenv,0);
    kenv->RestoreMatrix(var);
    kenv->ExecStack.Pop();

    if(flags&2)
      kenv->ExecStack.PushMul(mat1);
    else
    {
      kenv->ExecStack.Push(mat1);
      kenv->ExecStack.Top().l.Add3(matrix.l);
    }
    kenv->SetMatrix(mat0,var);
    ExecSceneInput(op,kenv,1);
    //op->ExecInput(kenv,1);
    kenv->RestoreMatrix(var);
    kenv->ExecStack.Pop();
  }
}


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

GenScene * __stdcall Init_Scene_Walk(GenScene *scene0,
  sU32 Flags,sInt FootCount,sF32 StepTresh,sF32 RunLowTresh,sF32 RunHighTresh,
  sInt2 ft,sF322 sl,sF322 ss,sF322 sn,
  sF323 l0,sF323 l1,sF323 l2,sF323 l3,sF323 l4,sF323 l5,sF323 l6,sF323 l7,
  sF32 scanup,sF32 scandown,
  KSpline *stepspline)
{
  GenScene *scene;
  GenScene *add;

  sREGZONE(ExecWalk);

  scene = new GenScene();

  add =  MakeScene(scene0);
  if(add) *scene->Childs.Add() = add;

  return scene;
}

#pragma lekktor(off)
struct WalkMem : public KInstanceMem
{
//  KKriegerParticle FootPart[8];
//  KKriegerParticle BodyPart;
  sVector FootOld[8];
  sVector FootNew[8];
  sInt StepTime[8];
  sInt LastTime;
  sVector OldPos,NewPos,FootCenter;
  sInt State;                   // state 0=walk, 1=run
  sInt LastLeg;                 // 0 = left; 1 = right
  sInt NextLeg;                 // (LastLeg+1)%FootGroup
  sInt Idle;                    // no new steps issued last frame
  sVector Deviation;            // avoid walls
  sInt DeviationFlag;
  sInt InitSteps;
  sVector FootDelta[8];         // position of the foot-contact point relative to body
} *mem;

void __stdcall Exec_Scene_Walk(KOp *op,KEnvironment *kenv,
  sU32 Flags,sInt FootCount,sF32 StepTresh,sF32 RunLowTresh,sF32 RunHighTresh,
  sInt2 fg,sF322 sl,sF322 ss,sF322 sn,
  sF323 l0,sF323 l1,sF323 l2,sF323 l3,sF323 l4,sF323 l5,sF323 l6,sF323 l7,
  sF32 scanup,sF32 scandown,
  KSpline *stepspline)
{
  sMatrix mat;//,save;               // local vars
  sMatrix dir;                    // desired orientation of walker
  sVector savevar[8],savetime;
  sVector v,sum,foot[8],spline[8],stepdir;
//  sVector plane;
  sInt i,j;
  sInt bestleg;
  sInt time;
  sF32 dist,f,t;
  sInt changestate;
//  KKriegerCellAdd *cell;

  sZONE(ExecWalk);

  // copy of instance-vars

  sInt State;
  sInt LastLeg;
  sInt NextLeg;

  // parameters

  sInt FootGroup[2];              // groups of feet.
  sF32 StepLength[2];             // max. length of a step
  sInt StepSpeed[2];              // duration of a step in ms.
  sInt StepNext[2];               // when to start the next step in ms. steps may overlap

  // init parameters

  FootGroup[0] = fg.x;
  FootGroup[1] = fg.y;
  StepLength[0] = sl.x;
  StepLength[1] = sl.y;
  StepSpeed[0] = sFtol(ss.x*1000);
  StepSpeed[1] = sFtol(ss.y*1000);
  StepNext[0] = sMin(sFtol(sn.x*1000),StepSpeed[0]);
  StepNext[1] = sMin(sFtol(sn.y*1000),StepSpeed[1]);

// init instance storage
  sMatrix &matrix = kenv->ExecStack.Top();

  mem = kenv->GetInst<WalkMem>(op);
  if(mem->Reset || (Flags&0x100) || kenv->TimeReset)
  {
//    cell = 0;
//    if(Flags&2)
//      cell = kenv->Game->FindCell(matrix.l);
    sum.Init();
    mem->FootCenter.Init();
    mem->InitSteps = FootCount*2;
    for(i=0;i<FootCount;i++)
    {
      mem->FootDelta[i].Init((&l0)[i].x,(&l0)[i].y,(&l0)[i].z,0);
      v.Add3(matrix.l,mem->FootDelta[i]);
      v.w = 0;
      mem->FootOld[i] = mem->FootNew[i] = v;
      mem->StepTime[i] = StepSpeed[0];
//      mem->FootPart[i].Init(v,sum,cell);
      mem->FootCenter.x += mem->FootDelta[i].x/FootCount;
      mem->FootCenter.z += mem->FootDelta[i].z/FootCount;
    }
//    mem->FootPart[FootCount-1].EndMarker = 1;
//    mem->BodyPart.Init(matrix.l,sum,cell);
//    mem->BodyPart.EndMarker = 1;
    mem->LastTime = kenv->CurrentTime;
    mem->NewPos = matrix.l;
    mem->State = 0;
    mem->LastLeg = 0;
    for(i=0;i<FootCount;i++)
      mem->FootDelta[i].Sub3(mem->FootCenter);
  }

  State = mem->State;
  LastLeg = mem->LastLeg;
  NextLeg = mem->NextLeg;

  // fake for animation

#if !sINTRO
  if(Flags & 0x30)
  {
    sInt k;
    State = ((Flags&0x30)==0x20);
    j = FootGroup[State];
    f = (StepTresh+RunLowTresh)/2;
    if(j)
      f = (RunLowTresh+RunHighTresh)/2;
    f = f/j;
    if(Flags&0x40)
      f *= 2;

    time = kenv->CurrentTime % (StepNext[State]*j);
    k = kenv->CurrentTime / (StepNext[State]*j);
    for(i=0;i<FootCount;i++)
    {
      mem->StepTime[i] = time - (StepNext[State]*(i%j));
      if(Flags&0x80)
        mem->FootOld[i].Init(0,0,f*(i%j-(time*1.0f/StepNext[State])-(j-1)*0.5f));
      else
        mem->FootOld[i].Init(0,0,f*(k*j+(i%j)));
      mem->FootOld[i].Add3(mem->FootDelta[i]);
      mem->FootNew[i] = mem->FootOld[i];
      mem->FootNew[i].z += f*j;
      if(mem->StepTime[i]<StepSpeed[State]-StepNext[State]*j)
      {
        mem->StepTime[i] += StepNext[State]*j;
        mem->FootOld[i].z -= f*j;
        mem->FootNew[i].z -= f*j;
      }
      if(mem->StepTime[i]<0)
      {
        mem->FootNew[i] = mem->FootOld[i];
        mem->StepTime[i] = StepSpeed[State];
      }
      if(mem->StepTime[i]>StepSpeed[State])
        mem->StepTime[i] = StepSpeed[State];
    }
  }
#endif

  // animate

  sum.Init();
  f = 0;
  for(i=0;i<FootCount;i++)          
  {
    t = sRange(1.0f*mem->StepTime[i]/StepSpeed[State],1.0f,0.0f);
    if(i<FootGroup[State])
    {
      f += t;
    }
#pragma lekktor(on)
    if(stepspline)
    {
      stepspline->Eval((t+State)*0.5f,spline[i]);
      if(mem->FootDelta[i].x<0) spline[i].x=-spline[i].x;
      if(mem->FootDelta[i].z<0) spline[i].z=-spline[i].z;
      spline[i].w = spline[i].w-State;
    }
    else
    {
      spline[i].x = 0;
      spline[i].y = sFSin(t*sPI)*0.25f; 
      spline[i].z = 0;
      spline[i].w = t;
    }
#pragma lekktor(off)

    foot[i].Lin3(mem->FootOld[i],mem->FootNew[i],spline[i].w);
    foot[i].w = 1.0f;
    /*
    if(Flags&0x02)
    {
      foot[i].y += spline[i].y;
      mem->FootPart[i].Control(foot[i]);
      foot[i].y -= spline[i].y;
    }
    */
    sum.Add3(foot[i]);
  }
  sum.Scale3(1.0f/FootCount);
  mat.i.Init(0,0,0,0);
  mat.j.Init(0,0,0,0); // ryg 040820
  mat.k.Init(0,0,0,0);
  mat.l.Init(sum.x,sum.y,sum.z,1);
  for(i=0;i<FootCount;i++)
  {
    v.Sub3(foot[i],sum);
    mat.i.AddScale3(v,mem->FootDelta[i].x);
    mat.j.AddScale3(v,mem->FootDelta[i].y);
    mat.k.AddScale3(v,mem->FootDelta[i].z);
  }
#pragma lekktor(on)
  switch((Flags>>2)&3)
  {
  case 0:   // straight, copy from input
    mat.i = matrix.i; 
    mat.j = matrix.j; 
    mat.k = matrix.k; 
    break;
  case 2:   // use xy
    mat.i.Unit3();
    mat.j.Init(0,1,0,0);
    mat.k.Cross4(mat.i,mat.j);
    mat.k.Unit3();
    mat.j.Cross4(mat.k,mat.i);
    mat.j.Unit3();
    break;
  case 1:   // use y
    mat.k = matrix.k;
  case 3:   // use xz
    mat.k.Unit3();
    mat.j.Cross4(mat.k,mat.i);
    mat.j.Unit3();
    mat.i.Cross4(mat.j,mat.k);
    mat.i.Unit3();
    break;
  }
#pragma lekktor(off)

  t = (f+LastLeg)/FootGroup[State];
  if(t>=2.0f) t-=2.0f;
  if(t>=1.0f) t-=1.0f;
  t = (t+State)*0.5f;
  savetime = kenv->Var[KV_LEG_TIMES];
  kenv->Var[KV_LEG_TIMES].Init(t,0,0,0);

  // advance

  time = kenv->CurrentTime-mem->LastTime;
  mem->LastTime = kenv->CurrentTime;
  if(time<0)
    time = 0;
  dir = matrix;
  if(mem->DeviationFlag>0)
  {
    dir.l.Add3(mat.l,mem->Deviation);
  }
/*
  if(mem->BodyPart.Cell)
  {
    dir.l.y += 0.5f;
    if(kenv->CurrentEvent && kenv->CurrentEvent->Monster)
      mem->BodyPart.SkipCell = &kenv->CurrentEvent->Monster->Cell;
    mem->BodyPart.Control(dir.l);
    dir.l.y -= 0.5f;
  }
  */
  dir.k.y = 0;
  dir.k.Unit3();
  dir.j.Init(0,1,0,0);
  dir.i.Cross4(dir.j,dir.k);
  for(i=0;i<FootCount;i++)
  {
    if(mem->StepTime[i]<StepSpeed[State])
      mem->StepTime[i] += time;
  }

  // find distance

  dist = 0;
  stepdir.Init();
  NextLeg = (LastLeg+1)%FootGroup[State];
  bestleg = NextLeg;
  for(i=0;i<FootCount;i++)
  {
    v.Rotate34(dir,mem->FootDelta[i]);
    v.Sub3(foot[i]);
    v.y = 0;
    f = v.Abs3();
    if(f>dist && mem->StepTime[i]>=StepSpeed[State])
    {
      dist = f;
      stepdir = v;
      bestleg = i;
    }
    savevar[i] = kenv->Var[KV_LEG_L0+i];
    v.Rotate3(dir,spline[i]);
    kenv->Var[KV_LEG_L0+i].Add3(foot[i],v);
    kenv->Var[KV_LEG_L0+i].w = spline[i].w;
  }

  // change walking/running

  changestate = 1;
  for(i=0;i<FootCount;i++)
    if(mem->StepTime[i]<StepSpeed[State])
      changestate = 0;
  if((State==0 && dist>RunHighTresh) || (State==1 && dist<RunLowTresh))
    changestate +=2;

  // changestate = 0 -> in animation                  ->
  // changestate = 1 -> animation done.               -> issue bestleg when idle
  // changestate = 2 -> in animation, change state    -> don't issue new steps
  // changestate = 3 -> animation done, change state  -> change state really


//  sDPrintF("%08x:state %d change %d dist %f | %4d %4d %4d %4d %4d %4d\n",mem,State,changestate,dist,mem->StepTime[0],mem->StepTime[1],mem->StepTime[2],mem->StepTime[3],mem->StepTime[4],mem->StepTime[5]);
  if(changestate==3)
  {
    if((State==0 && dist>RunHighTresh) || (State==1 && dist<RunLowTresh))
    {
      State=1-State;
      changestate = 0;
      LastLeg = LastLeg%FootGroup[State];
      for(i=0;i<FootCount;i++)
        mem->StepTime[i]=StepSpeed[State];
      NextLeg = bestleg%FootGroup[State];
    }
  }

  bestleg = bestleg%FootGroup[State];
  if(mem->Idle && changestate==1)
    NextLeg = bestleg;

  // do the next step

  mem->Idle = 0;
  if(changestate!=2 && mem->StepTime[LastLeg]>=StepNext[State] && mem->StepTime[NextLeg]>=StepSpeed[State])
  {
    if(mem->InitSteps>0)
    {
      mem->InitSteps--;
      dist = 1024;
    }
    if(mem->DeviationFlag>0)
      mem->DeviationFlag--;
    if(dist>StepTresh)
    {
      v.Sub3(dir.l,mat.l);
      f = v.Abs3();
      if((Flags & 1) && mem->InitSteps==0)
        NextLeg = bestleg;

      if(f>StepLength[State])
      {
        mem->NewPos = mat.l;
        mem->NewPos.AddScale3(v,StepLength[State]/f);
      }
      else
      {
        mem->NewPos = dir.l;
      }
//      mem->NewPos.y = 0;
      dir.l = mem->NewPos;
      j = mem->StepTime[LastLeg]-StepNext[State];
      for(i=NextLeg;i<FootCount;i+=FootGroup[State])
      {
        mem->FootOld[i] = foot[i];//mem->FootNew[i];
//        sDPrintF("soll: %06.3f %06.3f %06.3f --\n ist: %06.3f %06.3f %06.3f\n",mem->FootOld[i].x,mem->FootOld[i].y,mem->FootOld[i].z,dir.l.x,dir.l.y,dir.l.z);
        v = mem->FootDelta[i];
        v.y = 0;
        v.Rotate34(dir);
        /*
        if(mem->FootPart[i].Cell) 
        {
          KKriegerCellAdd *cell;
          sVector p0,p1;

          mem->FootNew[i] = v;        // do the step in any case...
          mem->StepTime[i] = j;

          kenv->Game->CollisionForMonsterMode = 1;      // and then find a better point--

          p0 = v; p0.y += scanup;
          cell = kenv->Game->FindCell(p0);

          if(cell)
          {
            p1 = v; p1.y -= scandown;
            if(kenv->Game->FindFirstIntersect(p0,p1,cell,&plane))
            {
              if(plane.y>0.75 && p1.y<p0.y)
              {
                mem->FootNew[i] = p1;
                mem->StepTime[i] = j;
              }
            }
          }

          kenv->Game->CollisionForMonsterMode = 0;
        }
        else*/
        {
          mem->FootNew[i] = v;
          mem->StepTime[i] = j;
        }
      }
      LastLeg = NextLeg;
    }
    else
    {
      mem->Idle = 1;
    }
  }

  // paint & cleanup
/*
  if(mem->BodyPart.Cell)
  {
//    kenv->Game->AddPart(&mem->FootPart[0]);
    kenv->Game->AddPart(&mem->BodyPart);
  }
*/

  kenv->ExecStack.Push(mat);

  //save = kenv->ExecMatrix;
  //kenv->ExecMatrix = mat;
  op->ExecInputs(kenv);
  kenv->ExecStack.Pop();
  //kenv->ExecMatrix = save;

  mem->State = State;
  mem->LastLeg = LastLeg;
  mem->NextLeg = NextLeg;
  for(i=0;i<FootCount;i++)
  {
    kenv->Var[KV_LEG_L0+i] = savevar[i];
  }
  kenv->Var[KV_LEG_TIMES] = savetime;
//  if(kenv->CurrentEvent)
//    kenv->CurrentEvent->ReturnMatrix = mat;
}
#pragma lekktor(on)

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

GenScene * __stdcall Init_Scene_Rotate(GenScene *scene0,sF323 dir,sInt axxis)
{
  GenScene *scene;
  GenScene *add;

  scene = new GenScene();

  add =  MakeScene(scene0);
  if(add) *scene->Childs.Add() = add;

  return scene;
}

void __stdcall Exec_Scene_Rotate(KOp *op,KEnvironment *kenv,sF323 dir,sInt axxis)
{
  sMatrix mat;//,save;
  sVector v;
  
  v.Init(dir.x,dir.y,dir.z,0);
  v.UnitSafe3();

  const sMatrix &matrix = kenv->ExecStack.Top();
  //save = kenv->ExecMatrix;

  switch(axxis)
  {
  case 0:
    mat.i = v;
    mat.k.Cross4(mat.i,matrix.j);
    mat.k.Unit3();
    mat.j.Cross4(mat.k,mat.i);
    mat.j.Unit3();
    break;
  case 1:
    mat.j = v;
    mat.i.Cross4(mat.j,matrix.k);
    mat.i.Unit3();
    mat.k.Cross4(mat.i,mat.j);
    mat.k.Unit3();
    break;
  case 2:
    mat.k = v;
    mat.j.Cross4(mat.k,matrix.i);
    mat.j.Unit3();
    mat.i.Cross4(mat.j,mat.k);
    mat.i.Unit3();
    break;
  default:
    mat = kenv->CurrentCam.CameraSpace;
    break;
  }
  mat.l = matrix.l;

  kenv->ExecStack.Push(mat);
  op->ExecInputs(kenv);
  kenv->ExecStack.Pop();
}

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

GenScene * __stdcall Init_Scene_Forward(GenScene *scene0,sF32 tresh)
{
  GenScene *scene;
  GenScene *add;

  scene = new GenScene();

  add =  MakeScene(scene0);
  if(add) *scene->Childs.Add() = add;

  return scene;
}

struct ForwardMem : KInstanceMem
{
  sVector oldpos;
};

void __stdcall Exec_Scene_Forward(KOp *op,KEnvironment *kenv,sF32 tresh)
{
  sMatrix mat;//,save;
  sVector v;
  sF32 f;
  ForwardMem *mem;

  //save = kenv->ExecMatrix;
  const sMatrix &matrix = kenv->ExecStack.Top();

  mem = kenv->GetInst<ForwardMem>(op);
#pragma lekktor(off)
  if(mem->Reset)
  {
    mem->oldpos = matrix.l;
    mem->oldpos.AddScale3(matrix.k,-tresh);
  }
#pragma lekktor(on)

  v.Sub3(matrix.l,mem->oldpos);
  f = v.UnitAbs3();
  mem->oldpos = matrix.l;
  mem->oldpos.AddScale3(v,-tresh);

  mat.j.Init(0,1,0,0);
  mat.k = v;
  mat.k.w = 0;
  mat.i.Cross4(mat.j,mat.k);
  mat.j.Cross4(mat.k,mat.i);
  mat.l = matrix.l;

  kenv->ExecStack.Push(mat);
  //kenv->ExecMatrix = mat;
  op->ExecInputs(kenv);
  kenv->ExecStack.Pop();
  //kenv->ExecMatrix = save;
}

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

#if sLINK_KKRIEGER

GenScene * __stdcall Init_Scene_Physic(GenScene *scene0,sInt flags,sF323 speed,sF323 scale,sF323 rmassf,sF32 mass,sInt partkind)
{
  GenScene *scene;
  GenScene *add;

  scene = new GenScene();
  if(scene0)
  {
    add =  MakeScene(scene0);
    if(add) *scene->Childs.Add() = add;
  }

  return scene;
}

struct PhysicMem : KInstanceMem
{
  sInt Flags;
  KKriegerCellDynamic Cell;
//  KKriegerParticle Part;
//  KKriegerPartBox Box;
};

void __stdcall Exec_Scene_Physic(KOp *op,KEnvironment *kenv,sInt flags,sF323 speedf,sF323 scalef,sF323 rmassf,sF32 mass,sInt partkind)
{
  sMatrix mat;//,save;
  PhysicMem *mem;
  sVector speed;
  sVector scale;
  KEvent *monsterevent;
  sMatrix m1;
//  sInt i;
  KKriegerCellDynamic *monstercell;

  speed.Init(speedf.x,speedf.y,speedf.z);
  scale.Init(scalef.x,scalef.y,scalef.z);

  monsterevent = 0;
  monstercell =0 ;
  if(flags & 0x20)
  {
    monsterevent = kenv->CurrentEvent;
    if(monsterevent && monsterevent->Monster==0)
      monstercell = &kenv->Game->Player.HitCell;
  }
  //save = kenv->ExecMatrix;
  //mat = save;
  mat = kenv->ExecStack.Top();
  speed.Rotate3(mat);
  mem = kenv->GetInst<PhysicMem>(op);
  m1.Init();
#pragma lekktor(off)
  if(mem->Reset)
  {
#pragma lekktor(on)
    mem->Flags = flags;
    switch(flags&3)
    {
    case 0:
    default:
      /*
      mem->Part.Init(mat.l,speed,kenv->Game->PlayerCell);
      mem->Part.EndMarker = 1;
      mem->Part.Mass = mass;
      mem->Part.Kind = partkind;
      mem->Part.ShotEvent = monsterevent;
      mem->Part.SkipCell = monstercell;
      */
      break;
    case 1:
      /*
      mem->Box.Init(mat,scale,speed,kenv->Game->PlayerCell);
      for(i=0;i<8;i++)
      {
        mem->Box.Part[i].Mass = mass;
        mem->Box.Part[i].Kind = partkind;
        mem->Box.Part[i].ShotEvent = monsterevent;
        mem->Box.Part[i].SkipCell = monstercell;
      }
      */
      break;
    case 2:
      mem->Cell.Init(m1,scale,KCM_SUB);
      mem->Cell.PrepareDynamic(mat);
      break;
    case 3:
      /*
      mem->Cell.Init(m1,scale,KCM_SUB);
      mem->Cell.PrepareDynamic(mat);
      mem->Box.Init(mat,scale,speed,kenv->Game->PlayerCell);
      for(i=0;i<8;i++)
      {
        mem->Box.Part[i].SkipCell = &mem->Cell;
        mem->Box.Part[i].Kind = partkind;
        mem->Box.Part[i].ShotEvent = monsterevent;
      }
      mem->Cell.PartBox = &mem->Box;
*/
      break;
    }
  }

  if(flags & 0x10)
    mem->Cell.Monster = kenv->EventMonster;

  switch(mem->Flags&3)
  {
  case 0:
  default:
/*
    kenv->Game->AddPart(&mem->Part);
    mat.l = mem->Part.Pos;
    mat.l.w = 1.0f;
*/
    break;
  case 1:
/*
    mem->Box.GetMatrix(mat);
    mem->Box.Register(kenv->Game);
*/
    break;
  case 2:
    mem->Cell.Matrix1 = mat;
    kenv->Game->AddDCell(&mem->Cell);
    break;
  case 3:
/*
    mat = mem->Cell.ConfirmedMatrix;
//    mem->Box.GetMatrix(mat);
//    mem->Cell.Matrix1 = mat;
    mem->Box.Register(kenv->Game);
    kenv->Game->AddDCell(&mem->Cell);
*/
    break;
  }

  kenv->ExecStack.Push(mat);
  op->ExecInputs(kenv);
  kenv->ExecStack.Pop();
//  if(op->GetInput(0))
//    op->GetInput(0)->Exec(kenv);      
}

#endif

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

GenScene * __stdcall Init_Scene_ForceLights(GenScene *in,sInt mode,sInt sw)
{
  return in;
}

void __stdcall Exec_Scene_ForceLights(KOp *op,KEnvironment *kenv,sInt mode,sInt sw)
{
  sInt inc;

  inc = (!mode || (kenv->Game && kenv->Game->Switches[sw])) ? 1 : 0;
  //kenv->ForceLights += inc;
  op->ExecInputs(kenv);
  //kenv->ForceLights -= inc;
}

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

GenScene * __stdcall Init_Scene_MatHack(GenMaterial *mtrl)
{
  if(mtrl)
    mtrl->Release();
  return new GenScene;
}

void __stdcall Exec_Scene_MatHack(KOp *op,KEnvironment *kenv)
{
  op->ExecInputs(kenv);
}

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

GenScene * __stdcall Init_Scene_Sector(GenScene *scene0)
{
  GenScene *scene,*add;
  
  scene = new GenScene;
  add = MakeScene(scene0);
  if(add) *scene->Childs.Add() = add;
  scene->IsSector = sTRUE;

  return scene;
}

void __stdcall Exec_Scene_Sector(KOp *op,KEnvironment *kenv)
{
  GenScene *scene;

  scene = (GenScene *)op->Cache;
  sVERIFY(scene && scene->ClassId == KC_SCENE);

  if(op->GetInput(0))
    Engine->AddSectorJob(scene,op->GetInput(0),kenv->ExecStack.Top());

  /*{
    sVERIFY(!kenv->CurrentSector);

    scene->Next = kenv->ExecSectorList;
    scene->Sector = op->GetInput(0);
    scene->SectorMatrix = kenv->ExecStack.Top();
    scene->SectorPaintThisFrame = sFALSE;
    kenv->ExecSectorList = scene;
  }*/
}

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

GenScene * __stdcall Init_Scene_Portal(GenScene *in,GenScene *s0,GenScene *s1,GenScene *si,sF322 x,sF322 y,sF322 z,sInt cost,sF32 door)
{
  if(s0) s0->Release();
  if(s1) s1->Release();
  if(si) si->Release();
  return in ? in : new GenScene;
}

void __stdcall Exec_Scene_Portal(KOp *op,KEnvironment *kenv,sF322 x,sF322 y,sF322 z,sInt cost,sF32 door)
{
  //KPortalJob *job;
  KOp *s[3];
  GenScene *sectors[3];
  
  s[0] = op->GetLink(0);
  s[1] = op->GetLink(1);
  s[2] = op->GetLink(2);

  if(s[2] && !s[2]->CheckOutput(KC_SCENE))
    s[2] = 0;

  if(s[0] && s[1] && s[0]->CheckOutput(KC_SCENE) && s[1]->CheckOutput(KC_SCENE))
  {
    for(sInt i=0;i<3;i++)
      sectors[i] = (s[i] && s[i]->Cache) ? (GenScene *)s[i]->Cache : 0;

    sAABox box;
    box.Min.Init(x.x,y.x,z.x,1.0f);
    box.Max.Init(x.y,y.y,z.y,1.0f);

    Engine->AddPortalJob(sectors,box,kenv->ExecStack.Top(),(door >= 0.02f) ? cost : 256);

#if !sPLAYER && 0 // FIXME!
    if(ShowPortalOp == op)
      ShowPortalJob = job;
#endif
  }
#if !sPLAYER // FIXME!
  if(ShowPortalOp == op)
  {
    for(sInt i=0;i<8;i++)
    {
      sVector p;
      p.Init((i&1) ? x.y : x.x,(i&2) ? y.y : y.x,(i&4) ? z.y : z.x,1);
      ShowPortalCube[i].Rotate34(kenv->ExecStack.Top(),p);
    }
    ShowPortalOpProcessed = sTRUE;
  }
#endif

  op->ExecInput(kenv,0);
}

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

GenScene * __stdcall Init_Scene_AdjustPass(GenScene *in,sInt adjust)
{
  return in;
}

void __stdcall Exec_Scene_AdjustPass(KOp *op,KEnvironment *kenv,sInt adjustPass)
{
  RenderPassAdjust += adjustPass;
  op->ExecInput(kenv,0);
  RenderPassAdjust -= adjustPass;
}

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

struct ScenePartMem : public KInstanceMem
{
  sInt Count;
  sInt OldCount;
  sInt Seed;
  sInt Mode;
  sF323 Rand;
  sF323 RandRot;
  sF323 RandSpeed;
  sF32 RandForw;
  sF323 RandRotSpeed;
  sF32 ComeOut;
  sVector *Pos;
  sVector *Speed;
  sVector *Rot;
  sVector *RotSpeed;
};

GenScene * __stdcall Init_Scene_Particles(GenScene *scene0,GenSpline *gspline,
  sInt mode,sInt count,sInt seed,
  sF323 rand,sF323 rot,sF323 rotspeed,sF32 anim,
  sF323 line,sF323 randspeed,sF323 gravity,
  sF32 forwspeed,sF32 comeout,sF323 randrotspeed,sF323 pulse,KSpline *spline)
{
  GenScene *scene;
  GenScene *add;

  scene = new GenScene();

  add =  MakeScene(scene0);
  if(add) *scene->Childs.Add() = add;
  if(gspline) gspline->Release();

  sREGZONE(InstanceMat);

  return scene;
}

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

void __stdcall Exec_Scene_Particles(KOp *op,KEnvironment *kenv,
  sInt mode,sInt count,sInt seed,
  sF323 rand,sF323 rot,sF323 rotspeed,sF32 anim,
  sF323 line,sF323 randspeed,sF323 gravity,
  sF32 forwspeed,sF32 comeout,sF323 randrotspeed,sF323 pulse,KSpline *spline)
{
  sInt i;
  sMatrix mat,mat0,mat1;
  ScenePartMem *mem;
  sF32 f;
  sVector v,v0;
  GenSpline *gsp;

  gsp = 0;
  if(op->GetInputCount()>=2)
    gsp = (GenSpline *)op->GetInput(1)->Cache;

  //spline = op->GetSpline(0);

  mem = kenv->GetInst<ScenePartMem>(op);
  if(mem->Reset || mem->Mode!=mode || mem->Count!=count || mem->Seed!=seed
     || mem->Rand.x!=rand.x || mem->Rand.y!=rand.y || mem->Rand.z!=rand.z
     || mem->RandRot.x!=rot.x || mem->RandRot.y!=rot.y || mem->RandRot.z!=rot.z
     || mem->RandForw!=forwspeed || mem->ComeOut != comeout
     || mem->RandRotSpeed.x!=randrotspeed.x || mem->RandRotSpeed.y!=randrotspeed.y || mem->RandRotSpeed.z!=randrotspeed.z )
  {
    if(!mem->Reset)
      delete[] mem->Pos;
    mem->Count = count;
    mem->Mode = mode;
    mem->Seed = seed;
    mem->Rand = rand;
    mem->RandRot = rot;
    mem->RandSpeed = randspeed;
    mem->RandForw = forwspeed;
    mem->RandRotSpeed = randrotspeed;
    mem->ComeOut = comeout;
    if(mode&0x10)
    {
      mem->Pos = new sVector[count*4];
      mem->Speed = mem->Pos+count;
      mem->Rot = mem->Pos+count*2;
      mem->RotSpeed = mem->Pos+count*3;
    }
    else
    {
      mem->Pos = new sVector[count*2];
      mem->Speed = mem->Pos+count;
      mem->Rot = 0;
      mem->RotSpeed = 0;
    }
    mem->DeleteArray = mem->Pos;
    sSetRndSeed(seed);
    for(i=0;i<count;i++)
    {
      do
      {
        mem->Pos[i].x = (sFGetRnd(2.0f)-1.0f);
        mem->Pos[i].y = (sFGetRnd(2.0f)-1.0f);
        mem->Pos[i].z = (sFGetRnd(2.0f)-1.0f);
        if((mode&12)==4)
          mem->Pos[i].Unit3();
      }
      while(((mode&12)==8) && mem->Pos[i].Dot3(mem->Pos[i])>1.0);

      if(mode&0x20)
        mem->Pos[i].w = 1.0f*i/count;
      else
        mem->Pos[i].w = sFGetRnd(1-comeout)+comeout;
      mem->Pos[i].x *= rand.x*0.5f;
      mem->Pos[i].y *= rand.y*0.5f;
      mem->Pos[i].z *= rand.z*0.5f;

      do
      {
        mem->Speed[i].x = (sFGetRnd(2.0f)-1.0f);
        mem->Speed[i].y = (sFGetRnd(2.0f)-1.0f);
        mem->Speed[i].z = (sFGetRnd(2.0f)-1.0f);
      }
      while(mem->Speed[i].Dot3(mem->Speed[i])>1.0);
      mem->Speed[i].w = sFGetRnd(1.0f);

      if(mem->Rot)
      {
        mem->Rot[i].x = sFGetRnd(rot.x);
        mem->Rot[i].y = sFGetRnd(rot.y);
        mem->Rot[i].z = sFGetRnd(rot.z);
      }
      if(mem->RotSpeed)
      {
        mem->RotSpeed[i].x = sFGetRnd(randrotspeed.x);
        mem->RotSpeed[i].y = sFGetRnd(randrotspeed.y);
        mem->RotSpeed[i].z = sFGetRnd(randrotspeed.z);
      }
    }
  }

  if(!(mode&0x80))
    anim = sFMod(anim,1.0f);
  if(anim<0) anim+=1;

  EngMesh *engmesh = 0;
  GenScene *myCache = (GenScene *) op->Cache;
  sMatrix *minmeshmat = 0,*origmat = 0;

  if((mode&0x100) && myCache && myCache->Childs.Count)
  {
    engmesh = myCache->Childs[0]->DrawMesh;
    if(engmesh)
      minmeshmat = origmat = engmesh->GetInstanceMatrices(mem->Count);
  }


  sVector save0,save1;
  save0 = kenv->Var[KV_SELECT];
  save1 = kenv->Var[KV_FRACTION];

  sZONE(InstanceMat);

  for(i=0;i<mem->Count;i++)
  {
    f = anim + mem->Pos[i].w + mem->RandForw*mem->Speed[i].w*anim;

    if(f<1.0f && (mode&64))
      continue;
    while(f>=1.0f)
      f -= 1.0f;

    mat.Init();
    sF32 g = (f*f);

    sVector vpos;
    vpos.x = mem->Pos[i].x + mem->Speed[i].x*f*randspeed.x+g*gravity.x;
    vpos.y = mem->Pos[i].y + mem->Speed[i].y*f*randspeed.x+g*gravity.y;
    vpos.z = mem->Pos[i].z + mem->Speed[i].z*f*randspeed.x+g*gravity.z;

    if(pulse.z!=0)
      vpos.Scale3(sFSin(anim*sPI2F*pulse.x + f*sPI2F*pulse.y)*pulse.z+1);

    if((mode&1) && spline)             // spline mode
    {
      spline->Eval(f,v);
      if(mode&0x10)
      {
        spline->Eval(f+0.01f,v0);
        v0.Sub3(v);
        mat.InitDir(v0);
      }
      mat.l.x += vpos.x + v.x;
      mat.l.y += vpos.y + v.y;
      mat.l.z += vpos.z + v.z;
      mat.l.w = 1;
    }
    if(gsp)             // genspline mode
    {
      sVector vp; 
      sF32 zoom;
      gsp->Eval(f,0,mat,zoom);

      vp.x = vpos.x;
      vp.y = vpos.y;
      vp.z = vpos.z;
      vp.w = 0;
      
      vp.Rotate3(mat);
      mat.l.Add3(vp);
    }
    else                                // line mode
    {
      mat.l.x += vpos.x + line.x*f;
      mat.l.y += vpos.y + line.y*f;
      mat.l.z += vpos.z + line.z*f;
      mat.l.w = 1;
    }
    if(mem->Rot||mem->RotSpeed)
    {
      sF32 rx,ry,rz;

      rx = f*rotspeed.x;
      ry = f*rotspeed.y;
      rz = f*rotspeed.z;
      if(mem->Rot)
      {
        rx += mem->Rot[i].x;
        ry += mem->Rot[i].y;
        rz += mem->Rot[i].z;
      }
      if(mem->RotSpeed)
      {
        rx += mem->RotSpeed[i].x*f;
        ry += mem->RotSpeed[i].y*f;
        rz += mem->RotSpeed[i].z*f;
      }
      mat0.InitEuler(rx,ry,rz);
//        mem->Rot[i].x+f*rotspeed.x*rrs,
//        mem->Rot[i].y+f*rotspeed.y*rrs,
//        mem->Rot[i].z+f*rotspeed.z*rrs);
      mat1.MulA(mat0,mat);
      mat = mat1;
    }

    if(minmeshmat)
    {
      *minmeshmat++ = mat;
    }
    else
    {
      kenv->Var[KV_SELECT].Init(i,i,i,i);
      kenv->Var[KV_FRACTION].Init(f,f,f,f);
      
      kenv->ExecStack.PushMul(mat);
      ExecSceneInputs(op,kenv,0);
      kenv->ExecStack.Pop();
    }
  }

  if(engmesh)
    engmesh->UpdateInstanceCount(minmeshmat - origmat);

  kenv->Var[KV_SELECT] = save0;
  kenv->Var[KV_FRACTION] = save1;

  if(engmesh)
    Engine->AddPaintJob(engmesh,kenv->ExecStack.Top(),0,0);
}

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

GenScene * __stdcall Init_Scene_ApplySpline(GenScene *add,GenSpline *sp)
{
  GenScene *scene;

  sp->Release();
  add = MakeScene(add);
  if(!add) return 0;

  scene = new GenScene;
  *scene->Childs.Add() = add;

  return scene;
}
 
void __stdcall Exec_Scene_ApplySpline(KOp *op,KEnvironment *kenv,sF32 time)
{
  sMatrix mat;
  GenSpline *sp;
  sF32 zoom;

  sp = (GenSpline *)op->GetInput(1)->Cache;
  sVERIFY(sp);

  sp->Eval(time,0,mat,zoom);

  kenv->ExecStack.PushMul(mat);
  ExecSceneInputs(op,kenv,0);
  kenv->ExecStack.Pop();
}

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

GenScene * __stdcall Init_Scene_Marker(GenScene *add,sInt marker)
{
  GenScene *scene = new GenScene;
  *scene->Childs.Add() = MakeScene(add);
  return scene;
}

void __stdcall Exec_Scene_Marker(KOp *op,KEnvironment *kenv,sInt marker)
{
  sVERIFY(marker>=0 && marker<sCOUNTOF(kenv->Markers));
  kenv->Markers[marker] = kenv->ExecStack.Top();
  ExecSceneInputs(op,kenv,0);
}

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

GenScene * __stdcall Init_Scene_LOD(GenScene *high,GenScene *low,sF32 lod)
{
  GenScene *scene = new GenScene;
  *scene->Childs.Add() = MakeScene(high);
  if(low)
    *scene->Childs.Add() = MakeScene(low);
  return scene;
}

void __stdcall Exec_Scene_LOD(KOp *op,KEnvironment *kenv,sF32 lod)
{
  sF32 dist;
  sVector v;
  sMatrix mat0;
  sMatrix mat1;

  mat0 = kenv->ExecStack.Top();
  mat1 = kenv->CurrentCam.CameraSpace;

  v.Sub3(mat1.l,mat0.l);
  dist = -v.Dot3(mat1.k);

  if(dist<lod)
    ExecSceneInput(op,kenv,0);
  else if(op->GetInputCount()==2)
    ExecSceneInput(op,kenv,1);
}

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

GenScene * __stdcall Init_Scene_Ambient(sU32 color)
{
  return new GenScene;
}

void __stdcall Exec_Scene_Ambient(KOp *op,KEnvironment *kenv,sU32 color)
{
  Engine->AddAmbientLight(color);
}

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