//---------------------------------------------------------------------------

#include <vcl.h>
#include <stdlib.h>
#include <Keyboard.hpp>
#pragma hdrstop

#include "fFurBall.h"
#include "dynodegl.hpp"        // Pascal unit
#include "GLVerletClasses.hpp"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma link "VerletHairClasses"
#pragma link "VerletClasses"
#pragma link "GLNavigator"
#pragma link "GLShadowPlane"
#pragma link "VectorGeometry"
#pragma link "GLExtrusion"
#pragma link "GLTexture"
#pragma link "GLCadencer"
#pragma link "GLMisc"
#pragma link "GLObjects"
#pragma link "GLScene"
#pragma link "GLWin32Viewer"
#pragma link "Keyboard"
#pragma link "GLVerletClasses"
#pragma link "dynodegl"
#pragma link "dynode"
#pragma resource "*.dfm"
TfrmFurBall *frmFurBall;

float random(void)
{
  return (float)(rand() & 0xFFF) / (float)0xFFF;
}

void __cdecl nearCallback(void *data, PdxGeom o1, PdxGeom o2)
{
  const int cCOL_MAX = 1;

  int i, numc;
  PdxBody b1, b2;
  TdContact contact[cCOL_MAX];
  TdJointID c;

  // exit without doing anything if the two bodies are connected by a joint
  b1 = dGeomGetBody(o1);
  b2 = dGeomGetBody(o2);
  if(b1 && b2 && (dAreConnected(b1, b2) != 0))
    return;

  for(i = 0; i <= cCOL_MAX - 1; i++)
  {
    contact[i].surface.mode = dContactBounce;

    // This determines friction, play around with it!
    contact[i].surface.mu = 3;  //10e9; //dInfinity; SHOULD BE INFINITY!
    contact[i].surface.mu2 = 0;
    contact[i].surface.bounce = 0.5;    //0.5;
    contact[i].surface.bounce_vel = 0.1;
  }

  numc = dCollide(o1, o2, cCOL_MAX, contact[0].geom, sizeof(TdContact));
  if(numc > 0)
  {
    for(i = 0; i <= numc - 1; i++)
    {
      c =
        dJointCreateContact(frmFurBall->world, frmFurBall->contactgroup,
                            &contact[i]);
      dJointAttach(c, b1, b2);
    }
  }
}

const float cOffset = 0.03;
//---------------------------------------------------------------------------
__fastcall TfrmFurBall::TfrmFurBall(TComponent * Owner):TForm(Owner)
{
  Show();

  randomize();
  InitODE("");
  
  world = dWorldCreate();
  space = dHashSpaceCreate(NULL);
  contactgroup = dJointGroupCreate(1000000);
  dWorldSetGravity(world, 0, 0, -9.81);

  CreateODEPlaneFromGLPlane(GLShadowPlane_Floor, space);
  CreateODEPlaneFromGLPlane(GLShadowPlane_Floor2, space);
  CreateODEPlaneFromGLPlane(GLShadowPlane_Wall, space);
  CreateODEPlaneFromGLPlane(GLShadowPlane_Wall2, space);
  CreateODEPlaneFromGLPlane(GLShadowPlane_Wall3, space);
  // dCreatePlane (space,0,0,1,0);

  VerletWorld = new TVerletWorld;
  VerletWorld->Iterations = 2;
  VerletWorld->VerletNodeClass = __classid(TGLVerletNode);

  CheckBox_FurGravityClick(this);
  CheckBox_WindResistenceClick(this);

  CreateVCPlaneFromGLPlane(GLShadowPlane_Floor, VerletWorld, cOffset);
  CreateVCPlaneFromGLPlane(GLShadowPlane_Floor2, VerletWorld, cOffset);
  CreateVCPlaneFromGLPlane(GLShadowPlane_Wall, VerletWorld, cOffset);
  CreateVCPlaneFromGLPlane(GLShadowPlane_Wall2, VerletWorld, cOffset);
  CreateVCPlaneFromGLPlane(GLShadowPlane_Wall3, VerletWorld, cOffset);

  HairList = new TList;

  CreateBall();
}

//---------------------------------------------------------------------------

void __fastcall TfrmFurBall::FormClose(TObject * Sender, TCloseAction & Action)
{
  GLCadencer1->Enabled = false;
  dJointGroupDestroy(contactgroup);
  dSpaceDestroy(space);
  dWorldDestroy(world);

  CloseODE();
}

//---------------------------------------------------------------------------

double angle = 0;
void __fastcall TfrmFurBall::GLCadencer1Progress(TObject * Sender,
                                                 const double deltaTime,
                                                 const double newTime)
{
  const float cTIME_STEP = 0.01;

  int i, j;
  float Delta;
  TVerletHair *Hair;
  TGLLines *GLLines;

  Delta = deltaTime;
  angle = angle + Delta * 3;

  while(PhysicsTime < newTime)
  {
    PhysicsTime = PhysicsTime + cTIME_STEP;

    if(!CheckBox_LockBall->Checked)
    {
      dSpaceCollide(space, NULL, nearCallback);
      dWorldStep(world, cTIME_STEP);    //}
      // remove all contact joints
      dJointGroupEmpty(contactgroup);

      if(IsKeyDown(VK_UP))
        dBodyAddForce(odeFurBallBody, 0, 0, 2.5);

      else if(IsKeyDown(VK_DOWN))
        dBodyAddForce(odeFurBallBody, 0, 0, -2.5);

      if(IsKeyDown('A'))
        dBodyAddForce(odeFurBallBody, 0, -1, 0);

      else if(IsKeyDown('D'))
        dBodyAddForce(odeFurBallBody, 0, 1, 0);

      if(IsKeyDown('W'))
        dBodyAddForce(odeFurBallBody, -1, 0, 0);

      else if(IsKeyDown('S'))
        dBodyAddForce(odeFurBallBody, 1, 0, 0);
    }

    PositionSceneObject(FurBall, odeFurBallGeom);
    VCSphere->Location = FurBall->Position->AsAffineVector;
    VerletWorld->Progress(cTIME_STEP, PhysicsTime);
  }

  for(i = 0; i <= HairList->Count - 1; i++)
  {
    Hair = (TVerletHair *) HairList->Items[i];
    GLLines = (TGLLines *) Hair->Data;
    for(j = 1; j <= Hair->NodeList->Count - 1; j++)
      GLLines->Nodes->Items[j - 1]->AsAffineVector =
        Hair->NodeList->Items[j]->Location;
  }
}

//---------------------------------------------------------------------------

void __fastcall TfrmFurBall::DC_LightHolderProgress(TObject * Sender,
                                                    const double deltaTime,
                                                    const double newTime)
{
// DC_LightHolder->Roll(deltaTime*pi*2*8);
}

//---------------------------------------------------------------------------

int FoldMouseX, FoldMouseY;
void __fastcall TfrmFurBall::GLSceneViewer1MouseMove(TObject * Sender,
                                                     TShiftState Shift, int X,
                                                     int Y)
{
  if(Shift.Contains(ssLeft))
    GLCamera1->MoveAroundTarget(FoldMouseY - Y, FoldMouseX - X);

  FoldMouseX = X;
  FoldMouseY = Y;
}

//---------------------------------------------------------------------------

void TfrmFurBall::CreateBall()
{
  TdMass m;

  dMassSetSphere(m, 1, FurBall->Radius);

  odeFurBallGeom = dCreateSphere(space, FurBall->Radius);
  odeFurBallBody = dBodyCreate(world);

  dGeomSetBody(odeFurBallGeom, odeFurBallBody);
  dBodySetMass(odeFurBallBody, &m);
  dBodySetLinearVel(odeFurBallBody, 0, 14, 0);

  dBodyAddTorque(odeFurBallBody, 0.1, 0.1, 0.1);

  // Add the GLScene object
  odeFurBallGeom->data = FurBall;

  CopyPosFromGeomToGL(odeFurBallGeom, FurBall);

  VCSphere = new TVCSphere(VerletWorld);
  VCSphere->Radius = FurBall->Radius * 1.1;
  VCSphere->Location = AffineVectorMake(FurBall->AbsolutePosition);

  CreateFur();
}

const int cRadiusMultiplier = 5, cSegmentCount = 4, cHairCount =
  200, cRootDepth = 4;

void TfrmFurBall::CreateRandomHair()
{
  int i;
  TAffineVector Dir;
  TVerletHair *Hair;
  TGLLines *GLLines;

  Dir = AffineVectorMake(random() - 0.5, random() - 0.5, random() - 0.5);
  NormalizeVector(Dir);

  TVHStiffnessSet vhs;
  vhs << vhsSkip1Node;
  Hair =
    new TVerletHair(VerletWorld, FurBall->Radius * cRootDepth,
                    FurBall->Radius * cRadiusMultiplier, cSegmentCount,
                    VectorAdd(AffineVectorMake(FurBall->AbsolutePosition),
                              VectorScale(Dir, FurBall->Radius)), Dir, vhs);

  //GLLines = TGLLines(GLScene1->Objects->AddNewChild(TGLLines));
  GLLines = (TGLLines *) DCShadowCaster->AddNewChild(__classid(TGLLines));
  GLLines->NodesAspect = lnaInvisible;
  GLLines->LineWidth = 2;
  GLLines->LineColor->Color = clrBlack;

  for(i = 0; i <= Hair->NodeList->Count - 1; i++)
    ((TGLVerletNode *) Hair->NodeList->Items[i])->GLBaseSceneObject = FurBall;

  for(i = 1; i <= Hair->NodeList->Count - 1; i++)
    GLLines->AddNode(Hair->NodeList->Items[i]->Location);

  for(i = 0; i <= GLLines->Nodes->Count - 1; i++)
    ((TGLLinesNode *) GLLines->Nodes->Items[i])->Color->Color = clrBlack;

  GLLines->ObjectStyle = GLLines->ObjectStyle << osDirectDraw;
  GLLines->SplineMode = lsmCubicSpline;

  Hair->Data = GLLines;
  HairList->Add(Hair);
}

void TfrmFurBall::CreateFur()
{
  TVerletHair *Hair;
  int i;

  for(i = 0; i <= HairList->Count - 1; i++)
  {
    Hair = ((TVerletHair *) HairList->Items[i]);
    delete((TGLLines *) Hair->Data);
    delete Hair;
  }

  HairList->Clear();

  for(i = 0; i <= cHairCount - 1; i++)
    CreateRandomHair();
}
void __fastcall TfrmFurBall::FormMouseWheel(TObject * Sender, TShiftState Shift,
                                            int WheelDelta, TPoint & MousePos,
                                            bool & Handled)
{
  GLCamera1->
    AdjustDistanceToTarget(Power
                           ((float)1.1, (float)((float)WheelDelta / 120.0)));
}

//---------------------------------------------------------------------------

void __fastcall TfrmFurBall::CheckBox_FurGravityClick(TObject * Sender)
{
  if(!CheckBox_FurGravity->Checked)
    delete Gravity;
  else
  {
    Gravity = new TVFGravity(VerletWorld);
    Gravity->Gravity = AffineVectorMake(0, 0, -9.81);
  }
}

//---------------------------------------------------------------------------

const float cMaxWindMag = 8;
void __fastcall TfrmFurBall::CheckBox_WindResistenceClick(TObject * Sender)
{
  if(!CheckBox_WindResistence->Checked)
    delete AirResistance;
  else
  {
    AirResistance = new TVFAirResistance(VerletWorld);
    AirResistance->DragCoeff = 0.01;
    AirResistance->WindDirection = AffineVectorMake(1, 0, 0);
    AirResistance->WindMagnitude =
      (float)(TrackBar_WindForce->Position) / 100.0 * cMaxWindMag;
    AirResistance->WindChaos = 0.4;
  }

  TrackBar_WindForce->Enabled = CheckBox_WindResistence->Checked;
}

//---------------------------------------------------------------------------

void __fastcall TfrmFurBall::TrackBar_WindForceChange(TObject * Sender)
{
  if(AirResistance)
    AirResistance->WindMagnitude =
      (float)(TrackBar_WindForce->Position) / 100.0 * cMaxWindMag;
}

//---------------------------------------------------------------------------

void __fastcall TfrmFurBall::CheckBox_BaldClick(TObject * Sender)
{
  for(int i = 0; i <= HairList->Count - 1; i++)
  {
    TVerletHair *h = (TVerletHair *) (HairList->Items[i]);
    {
      h->Anchor->NailedDown = !CheckBox_Bald->Checked;
      h->Anchor->OldLocation = h->Anchor->Location;
      h->Root->NailedDown = !CheckBox_Bald->Checked;
      h->Root->OldLocation = h->Root->Location;
    }
  }

  if(!CheckBox_Bald->Checked)
    VerletWorld->PauseInertia(5);
}

//---------------------------------------------------------------------------

void __fastcall TfrmFurBall::Timer1Timer(TObject * Sender)
{
  Label_FPS->Caption = GLSceneViewer1->FramesPerSecondText(1);
  GLSceneViewer1->ResetPerformanceMonitor();
}

//---------------------------------------------------------------------------

void __fastcall TfrmFurBall::CheckBox_ShadowsClick(TObject * Sender)
{
  TGLLightSource *light;

  if(CheckBox_Shadows->Checked)
    light = GLLightSource1;
  else
    light = NULL;

  GLShadowPlane_Floor->ShadowedLight = light;
  GLShadowPlane_Floor2->ShadowedLight = light;
  GLShadowPlane_Wall->ShadowedLight = light;
  GLShadowPlane_Wall2->ShadowedLight = light;
  GLShadowPlane_Wall3->ShadowedLight = light;
}

//---------------------------------------------------------------------------

void __fastcall TfrmFurBall::CheckBox_InertiaClick(TObject * Sender)
{
  VerletWorld->Inertia = CheckBox_Inertia->Checked;
}

//---------------------------------------------------------------------------

