{-------------------------------}
{------- Light Engine 2D -------}
{-------------------------------}
{                               }
{ version:  1.35                }
{ date:     22.02.2011          }
{                               }
{-------- developed by: --------}
{                               }
{     Vadim Burenkov aka Vad    }
{                               }
{ mail: vadim_burenkov@mail.ru  }
{                               }
{-------------------------------}
unit LightEngine2D;

{/$DEFINE STATIC}

//Directive allow you to use chipmunk with light engine
{/$DEFINE CHIPMUNK_SUPPORT}

{$IFDEF LINUX}
  {$MACRO ON}
  {$DEFINE LINUX_OR_DARWIN}
  {$DEFINE stdcall := cdecl}
{$ENDIF}
{$IFDEF MSWINDOWS}
  {$DEFINE WINDOWS}
{$ENDIF}
{$IFDEF DARWIN}
  {$MACRO ON}
  {$DEFINE LINUX_OR_DARWIN}
  {$DEFINE stdcall := cdecl}
  {$LINKFRAMEWORK OpenGL}
{$ENDIF}

interface
uses
  {$IFDEF CHIPMUNK_SUPPORT}
  zglChipmunk,
  {$ENDIF}
  DataLists,
  math,
  {$IFNDEF STATIC}
  zglHeader
  {$ELSE}
  zgl_main,
  zgl_opengl_all,
  zgl_camera_2d,
  zgl_textures,
  zgl_render_target,
  zgl_fx,
  zgl_primitives_2d,
  zgl_math_2d,
  zgl_sprite_2d
  {$ENDIF}
  ;


type
{$IFDEF CHIPMUNK_SUPPORT}
  leVect=cpVect; //Double = low speed!
{$ELSE}
leVect=record
   x,y:single;
end;
{$ENDIF}

type
  le_RGB=record
   r,g,b:single;
end;

type
  le_BB=record
   lt:leVect;
   rb:leVect;
end;

type
  le_ShadowDrawData=record
   verts: array [0..3] of leVect;
   pVerts: array [0..1, 0..2] of leVect;
   group:single;
end;

type
  PPolyShapeData=^TPolyShapeData;
   TPolyShapeData=record
    base_verts: array of leVect;
    verts: array of leVect;
    vCount:integer;
    pAngle, angle:single;
end;

type
  PCircleShapeData=^TCircleShapeData;
   TCircleShapeData=record
    radius:single;
end;

type
  PShadowShape=^TShadowShape;
   TShadowShape=record
    ShapeType:(lePolyShape,leCircleShape);
    CircleData:PCircleShapeData;
    PolyData:PPolyShapeData;
    position:leVect;
    group:single;
    bb:le_BB;
    {$IFDEF CHIPMUNK_SUPPORT}
    cpShape:PcpShape;
    {$ENDIF}
end;

type
  PLightSource=^TLightSource;
   TLightSource=record
    AlphaBuffer:zglPRenderTarget;
    position:leVect;
    radius:single;
    pRadius: single;
    sAngle, sSoft, angle: single; //for sector lights only
    res:integer;
    color:le_RGB;
    intensity:single;
    bb:le_BB;
    shapes: array of PShadowShape;
    shadows: array of le_ShadowDrawData;
    ShapesCount:integer;
    update, enable: boolean;
end;

//Vectors-----------------------------------------------------------------------
function le_v(x,y:single):leVect; overload;
function le_v:leVect;             overload;
function le_vAdd(v1,v2:leVect):leVect;
function le_vSub(v1,v2:leVect):leVect;
function le_vMult(v1:leVect; n:single):leVect;
function le_vScale(v1,v2:leVect):single;
function le_vPerp(v1:leVect):leVect;
function le_vLength(v1:leVect):single;
function le_vNormalize(v1:leVect):leVect;
function le_vRotate(v1,v2:leVect):leVect;
function le_vToLight(v:leVect; t:PLightSource):leVect;

//User Functions and Procedures-------------------------------------------------
//Colors
function le_IntToRGB( c:integer ): le_RGB;
function le_ColorRGB( r, g, b:single ): le_RGB; overload;
function le_ColorRGB(i:single):le_RGB; overload;

//Groups
function le_GetNewGroup: single;

//Main
procedure le_InitLightEngine( DarkColor: le_RGB; PenumbraTexture: string; LightsQuality, CircleShapesQuality: integer);
procedure le_RenderLight( t: PLightSource );
procedure le_RenderShadows;
procedure le_Update;

//Light sources
function  le_CreateLightSource( p: leVect; radius: single; color: le_RGB ): PLightSource;
procedure le_ResizeLightSource( t: PLightSource );
procedure le_UpdateLightSource( t: PLightSource ); //WARNING: Do not delete objects after executing this procedure!
procedure le_FreeLightSource( t: PLightSource );
procedure le_EachLightSource( p: dl_proc );

//Shadow shapes
function  le_CreateShadowShape( v:array of leVect; vn: integer; p: leVect; group: single ): PShadowShape; overload;
function  le_CreateShadowShape( r:single; p:leVect; group: single ):PShadowShape; overload;
{$IFDEF CHIPMUNK_SUPPORT}
function  le_CreateShadowShape( cpShape: PcpShape; group: single ): PShadowShape; overload;
{$ENDIF}
procedure le_UpdateShadowShape( t: PShadowShape );
procedure le_DrawShadowShape( t: PShadowShape );
procedure le_FreeShadowShape( t: PShadowShape );
procedure le_EachShadowShape( p: dl_proc );

//Internal functions and procedures---------------------------------------------
function le_GetShadowVerts( l: PLightSource; s: PShadowShape ): le_ShadowDrawData;
procedure le_DrawShadows( t: PLightSource );

procedure le_DrawLightSource( t: PLightSource );
procedure le_zDrawShadowShape( t: PShadowShape; l: PlightSource );

//Boundary Box
function le_UpdateBB( v:array of leVect; vn:integer ): le_BB; overload;
function le_UpdateBB( p: leVect; r: single ): le_BB; overload;
procedure le_DrawBB( bb: le_BB );
procedure le_DrawShapeBB( t: PShadowShape );
procedure le_DrawLightBB( t: PLightSource );
function le_CollideBB( bb1, bb2: le_BB ): boolean;

Var
  le_Lights: TDataList;
  le_Shapes: TDataList;

  le_AccBuffer: zglPRenderTarget;
  le_DarkColor: le_RGB;

  le_CircleSinCos: array of leVect;
  le_LightSinCos: array of leVect;

  le_GroupsCount: integer=0;

  le_ShadowsUpdate: integer;
  le_ShadowsNotUpdate: integer;
  le_LightsUpdate: integer;
  le_scrBB: le_BB;

  le_LightSubdivisions: integer;
  le_CircleSubdivisions: integer;

  le_Camera: zglPCamera2D;
  le_Widht, le_Height: integer;

  le_PenTex, le_lPenTex: zglPTexture;
  le_SoftShadows: boolean;

implementation

{$IFNDEF STATIC}
const
  {$IFDEF LINUX}
  libGL  = 'libGL.so.1';
  {$ENDIF}
  {$IFDEF WINDOWS}
  libGL  = 'opengl32.dll';
  {$ENDIF}
  {$IFDEF DARWIN}
  libGL  = '/System/Library/Frameworks/OpenGL.framework/Libraries/libGL.dylib';
  {$ENDIF}

  GL_LEQUAL                         = $0203;
  GL_NOTEQUAL                       = $0205;
  GL_ALWAYS                         = $0207;
  GL_BLEND                          = $0BE2;
  GL_TRIANGLE_FAN                   = $0006;
  GL_QUADS                          = $0007;
  GL_TRIANGLES                      = $0004;
  GL_TEXTURE_2D                     = $0DE1;

type
  GLenum     = Cardinal;      PGLenum     = ^GLenum;
  GLboolean  = Byte;          PGLboolean  = ^GLboolean;
  GLbitfield = Cardinal;      PGLbitfield = ^GLbitfield;
  GLbyte     = ShortInt;      PGLbyte     = ^GLbyte;
  GLshort    = SmallInt;      PGLshort    = ^GLshort;
  GLint      = Integer;       PGLint      = ^GLint;
  GLsizei    = Integer;       PGLsizei    = ^GLsizei;
  GLubyte    = Byte;          PGLubyte    = ^GLubyte;
  GLushort   = Word;          PGLushort   = ^GLushort;
  GLuint     = Cardinal;      PGLuint     = ^GLuint;
  GLfloat    = Single;        PGLfloat    = ^GLfloat;
  GLclampf   = Single;        PGLclampf   = ^GLclampf;
  GLdouble   = Double;        PGLdouble   = ^GLdouble;
  GLclampd   = Double;        PGLclampd   = ^GLclampd;
{ GLvoid     = void; }        PGLvoid     = Pointer;
                              PPGLvoid    = ^PGLvoid;

  procedure glBegin(mode: GLenum); stdcall; external libGL;
  procedure glEnd; stdcall; external libGL;
  procedure glEnable(cap: GLenum); stdcall; external libGL;
  procedure glDisable(cap: GLenum); stdcall; external libGL;
  procedure glDepthFunc(func: GLenum); stdcall; external libGL;
  procedure glDepthMask(flag: GLboolean); stdcall; external libGL;
  procedure glColor4f(red, green, blue, alpha: GLfloat); stdcall; external libGL;
  procedure glVertex2f(x, y: GLfloat); stdcall; external libGL;
  procedure glVertex3f(x, y, z: GLfloat); stdcall; external libGL;
  procedure glBindTexture(target: LongWord; texture: GLuint); stdcall; external libGL;
  procedure glTexCoord2f(s, t: GLfloat); stdcall; external libGL;
{$ENDIF}

function le_v(x,y:single):leVect; overload;
begin
  result.x:=x;
  result.y:=y;
end;

function le_v:leVect; overload;
begin
  result.x:=0;
  result.y:=0;
end;

function le_vAdd(v1,v2:leVect):leVect;
begin
  result:=le_v(v1.x+v2.x,v1.y+v2.y);
end;

function le_vRotate(v1,v2:leVect):leVect;
begin
  Result.x := v1.x*v2.x - v1.y*v2.y;
  Result.y := v1.x*v2.y + v1.y*v2.x;
end;

function le_vSub(v1,v2:leVect):leVect;
begin
  result:=le_v(v1.x-v2.x,v1.y-v2.y);
end;

function le_vMult(v1:leVect; n:single):leVect;
begin
  result:=le_v(v1.x*n,v1.y*n);
end;

function le_vScale(v1,v2:leVect):single;
begin
  result:=v1.x*v2.x+v1.y*v2.y;
end;

function le_vPerp(v1:leVect):leVect;
begin
  result:=le_v(-v1.y,v1.x)
end;

function le_vLength(v1:leVect):single;
begin
  result:=sqrt(sqr(v1.x)+sqr(v1.y));
end;

function le_vNormalize(v1:leVect):leVect;
begin
  result:=le_vMult(v1,1/le_vLength(v1));
end;

function le_vToLight(v:leVect; t:PLightSource):leVect;
begin
  result:=le_vAdd(le_vSub(v,t.position),le_v(t.res/2,t.res/2));
end;

function le_IntToRGB(c:Integer):le_RGB;
begin
  result.r := ((c and $FF0000) shr 16) / 255;
  result.g := ((c and $FF00) shr 8) / 255;
  result.b := (c and $FF) / 255;
end;

function le_ColorRGB(r,g,b:single):le_RGB; overload;
begin
 result.r:=r;
 result.g:=g;
 result.b:=b;
end;

function le_ColorRGB(i:single):le_RGB; overload;
begin
 result.r:=i;
 result.g:=i;
 result.b:=i;
end;

function le_GetNewGroup:single;
begin
  inc(le_GroupsCount);
  result:=0.001*le_GroupsCount;
end;

procedure le_InitLightEngine( DarkColor: le_RGB; PenumbraTexture: string; LightsQuality, CircleShapesQuality: integer);
var
  i: integer;
begin
  le_DarkColor:=DarkColor;
  le_LightSubdivisions:=LightsQuality;
  le_CircleSubdivisions:=CircleShapesQuality;
  le_Camera:=cam2d_Get;
  le_Widht:=zgl_Get( VIEWPORT_WIDTH );
  le_Height:=zgl_Get( VIEWPORT_HEIGHT );
  le_AccBuffer := rtarget_Add( tex_CreateZero( le_Widht, le_Height, 0, TEX_DEFAULT_2D ) , RT_FULL_SCREEN );

  SetLength(le_CircleSinCos,le_CircleSubdivisions);
  for i:=0 to le_CircleSubdivisions-1 do
  begin
   le_CircleSinCos[i].x := sin(((pi*2)/le_CircleSubdivisions)*i);
   le_CircleSinCos[i].y := cos(((pi*2)/le_CircleSubdivisions)*i);
  end;
  SetLength(le_LightSinCos,le_LightSubdivisions);
  for i:=0 to le_LightSubdivisions-1 do
  begin
   le_LightSinCos[i].x := sin(((pi*2)/le_LightSubdivisions)*i);
   le_LightSinCos[i].y := cos(((pi*2)/le_LightSubdivisions)*i);
  end;
  if PenumbraTexture='' then le_SoftShadows:=false else
  begin
   le_PenTex  := tex_LoadFromFile( PenumbraTexture, $FF000000, TEX_DEFAULT_2D );
   le_lPenTex := tex_LoadFromFile( PenumbraTexture, $FF000000, TEX_DEFAULT_2D or TEX_INVERT );
   le_SoftShadows:=true;
  end;
end;

procedure le_RenderLight(t:PLightSource);
var
  i:integer;
begin
  if not t.update then exit;
  if not t.enable then exit;

  zgl_Enable( DEPTH_BUFFER );
  rtarget_Set( t.alphaBuffer );

  //Clear RGBA
  zgl_Enable( DEPTH_BUFFER );
  zgl_Enable( DEPTH_MASK );
  glDepthFunc(GL_ALWAYS);

  glBegin(GL_QUADS);
  glColor4f(0,0,0,1);
  glVertex3f(0,0,0);
  glVertex3f(0 ,t.res,0);
  glVertex3f(t.res ,t.res,0);
  glVertex3f(t.res,0,0);
  glEnd;

  zgl_Disable( DEPTH_MASK );
  zgl_Disable( DEPTH_BUFFER );

  le_DrawLightSource(t);

  zgl_Enable( DEPTH_BUFFER );
  zgl_Enable( DEPTH_MASK );
  glDepthFunc(GL_ALWAYS);

  glEnable( GL_BLEND );
  for i:=0 to t.ShapesCount-1 do le_zDrawShadowShape(t.Shapes[i],t);
  glDisable( GL_BLEND );

  zgl_Disable( DEPTH_MASK );
  le_DrawShadows(t);
  zgl_Disable( DEPTH_BUFFER );

  rtarget_Set( nil );
  zgl_Disable( DEPTH_BUFFER );
  glDepthFunc( GL_LEQUAL );

  rtarget_Set( le_AccBuffer );
  fx_SetBlendMode(FX_BLEND_ADD);
  ssprite2d_Draw( t.alphaBuffer.Surface, round(t.position.x-le_Camera.X-t.res/2),round(t.position.y-le_Camera.Y-t.res/2), t.res,t.res, 0 );
  fx_SetBlendMode(FX_BLEND_NORMAL);
  rtarget_Set( nil );
end;

procedure le_RenderShadows;
begin
  fx_SetBlendMode(FX_BLEND_MULT);
  ssprite2d_Draw( le_AccBuffer.Surface, le_Camera.X,le_Camera.Y,le_Widht, le_Height,0);
  fx_SetBlendMode(FX_BLEND_NORMAL);

  rtarget_Set( le_AccBuffer );

  glBegin(GL_QUADS);
  glColor4f(le_DarkColor.r,le_DarkColor.g,le_DarkColor.b,1);
  glVertex2f(0,0);
  glVertex2f(0 ,le_Height);
  glVertex2f(le_Widht ,le_Height);
  glVertex2f(le_Widht,0);
  glEnd;

  rtarget_Set( nil );
end;

procedure le_Update;
begin
  le_ShadowsUpdate:=0;
  le_LightsUpdate:=0;
  le_Camera:=cam2d_Get;
  le_Widht:=zgl_Get( VIEWPORT_WIDTH );
  le_Height:=zgl_Get( VIEWPORT_HEIGHT );
  le_scrBB.lt:=le_v(le_Camera.X,le_Camera.Y);
  le_scrBB.rb:=le_v(le_Camera.X+le_Widht,le_Camera.Y+le_Height);

  le_EachShadowShape( @le_UpdateShadowShape );
  le_EachLightSource( @le_UpdateLightSource );
end;

function le_CreateLightSource( p: leVect; radius: single; color: le_RGB ): PLightSource;
var
  t:PLightSource;
begin
  new(t);

  t.ShapesCount:=0;
  t.position:=p;
  t.radius:=radius;
  t.pRadius:=20;
  t.Angle:=0;
  t.sAngle:=0;
  t.intensity:=1;
  t.res:=round(t.radius*2);
  t.color:=color;
  t.alphaBuffer:=rtarget_Add( tex_CreateZero( t.res , t.res, 0,   TEX_DEFAULT_2D ) ,   RT_USE_DEPTH);
  t.enable:=true;

  DataList_Add( le_Lights, t );
  result:=t;
end;

procedure le_ResizeLightSource(t:PLightSource);
begin
  t.res:=round(t.radius*2);
  tex_del(t.alphaBuffer.Surface);
  rtarget_Del(t.alphaBuffer);
  t.alphaBuffer:=rtarget_Add( tex_CreateZero( t.res, t.res, 0, TEX_DEFAULT_2D ) , RT_USE_DEPTH);
end;

procedure le_UpdateLightSource(t:PLightSource);
var
  Shape:PShadowShape;
  i:integer;
  BBVect: array [0..3] of leVect;
begin
  if not t.enable then exit;
  t.ShapesCount:=0;

  if t.sAngle=0 then t.bb:=le_UpdateBB(t.position,t.radius) else
  begin
   BBVect[0]:=t.position;
   BBVect[1]:=le_vAdd(t.position, le_v(t.radius*cos(t.sAngle+t.Angle), t.radius*sin(t.sAngle+t.Angle)));
   BBVect[2]:=le_vAdd(t.position, le_v(t.radius*cos(-t.sAngle+t.Angle), t.radius*sin(-t.sAngle+t.Angle)));
   BBVect[3]:=le_vAdd(t.position, le_v(t.radius*cos(t.Angle), t.radius*sin(t.Angle)));
   t.bb:=le_UpdateBB(BBVect,4);
  end;

  t.Update := le_CollideBB(t.bb,le_scrBB);
  if t.Update = false then exit;
  inc(le_LightsUpdate);

  SetLength(t.Shapes,le_Shapes.Count);
  SetLength(t.Shadows,le_Shapes.Count);
  for i:=0 to le_Shapes.DLength-1 do if le_Shapes.Data[i]<>nil then
  begin
   Shape := PShadowShape( le_Shapes.Data[i] );
   if le_CollideBB(t.bb, Shape.bb) then
   begin
    inc(t.ShapesCount);
    t.Shapes[t.ShapesCount-1]:=Shape;
    t.Shadows[t.ShapesCount-1]:=le_GetShadowVerts(t,Shape);
   end;
  end;

  SetLength(t.Shapes,t.ShapesCount);
  SetLength(t.Shadows,t.ShapesCount);
  le_ShadowsUpdate:=le_ShadowsUpdate+t.ShapesCount;
end;

procedure le_FreeLightSource(t:PLightSource);
begin
  DataList_Free( le_Lights, DataList_GetID( le_Lights, t ) );
  Dispose(t);
end;

procedure le_EachLightSource( p: dl_proc );
begin
  DataList_Each( le_Lights, p );
end;

function le_CreateShadowShape(v:array of leVect;vn:integer;p:leVect;group:single):PShadowShape; overload;
var
  t:PShadowShape;
  pShape:PPolyShapeData;
  i:integer;
begin
  new(t);
  FillChar( t^, sizeof(t^), 0 );

  new(pShape);
  pShape.Angle:=0;
  pShape.vCount:=vn;
  SetLength(pShape.base_Verts,pShape.vCount);
  SetLength(pShape.Verts,pShape.vCount);
  for i:=0 to pShape.vCount-1 do pShape.base_Verts[i]:=v[i];

  t.PolyData:=pShape;
  t.PolyData.pAngle:=pi;
  t.PolyData.Angle:=0;
  t.ShapeType:=lePolyShape;
  t.Position:=p;
  t.Group:=group;

  DataList_Add( le_Shapes, t );
  result:=t;
end;

function le_CreateShadowShape(r:single;p:leVect;group:single):PShadowShape; overload;
var
  t:PShadowShape;
  cShape:PCircleShapeData;
begin
  new(t);
  FillChar( t^, sizeof(t^), 0 );

  new(cShape);
  cShape.radius:=r;

  t.CircleData:=cShape;
  t.ShapeType:=leCircleShape;
  t.Position:=p;
  t.Group:=group;

  DataList_Add( le_Shapes, t );
  result:=t;
end;

{$IFDEF CHIPMUNK_SUPPORT}
function le_CreateShadowShape(cpShape:PcpShape;group:single):PShadowShape; overload;
var
  t:PShadowShape;
begin
  new(t);
  FillChar( t^, sizeof(t^), 0 );

  if cpShape.klass._type =CP_POLY_SHAPE then
  begin
   t.ShapeType:=lePolyShape;
   t.PolyData:=new(PPolyShapeData);
   t.PolyData.vCount:=PcpPolyShape(cpShape).numVerts;
   SetLength(t.PolyData.verts,t.PolyData.vCount);
  end;

  if cpShape.klass._type=CP_CIRCLE_SHAPE then
  begin
   t.ShapeType:=leCircleShape;
   t.CircleData:=new(PCircleShapeData);
  end;
  t.cpShape:=cpShape;
  t.Group:=group;

  DataList_Add( le_Shapes, t );
  result:=t;
end;
{$ENDIF}

procedure le_UpdateShadowShape(t:PShadowShape);
var
  i:integer;
  rot:leVect;
  {$IFDEF CHIPMUNK_SUPPORT}
  cpPoly   : PcpPolyShape;
  cpCircle : PcpCircleShape;
  {$ENDIF}
begin
  if t.ShapeType=lePolyShape then
  begin
   {$IFDEF CHIPMUNK_SUPPORT}
   if t.cpShape<>nil then
   begin
    //copy vertex data from chipmunk
    cpPoly:=PcpPolyShape(t.cpShape);
    for i:=0 to cpPoly.numVerts-1 do t.PolyData.Verts[i]:=cpPoly.tVerts[i];
//    t.bb.lt:=le_v(t.cpShape.bb.l,t.cpShape.bb.t);
//    t.bb.rb:=le_v(t.cpShape.bb.r,t.cpShape.bb.b);
      t.bb:=le_UpdateBB( t.PolyData.Verts,t.PolyData.vCount );
   end
   else
   {$ENDIF}
   begin
    if t.PolyData.Angle<>t.PolyData.pAngle then
    begin
     rot := le_v( cos(t.PolyData.Angle), sin(t.PolyData.Angle) );
     for i := 0 to t.PolyData.vCount - 1 do
     t.PolyData.Verts[i] := le_vAdd( t.position, le_vRotate( t.PolyData.base_Verts[i], rot ));
     t.PolyData.pAngle:=t.PolyData.Angle;
    end;
    t.bb:=le_UpdateBB( t.PolyData.Verts,t.PolyData.vCount );
   end;
  end;

  if t.ShapeType=leCircleShape then
   begin
    {$IFDEF CHIPMUNK_SUPPORT}
    if t.cpShape<>nil then
    begin
     cpCircle:=PcpCircleShape(t.cpShape);
     t.CircleData.radius:=cpCircle.r;
     t.Position:=cpCircle.tc;
     t.bb.lt:=le_v(t.cpShape.bb.l,t.cpShape.bb.t);
     t.bb.rb:=le_v(t.cpShape.bb.r,t.cpShape.bb.b);
    end
    else
    {$ENDIF}
    begin
     t.bb:=le_UpdateBB( t.Position, t.CircleData.radius );
    end;
   end;
end;

procedure le_DrawShadowShape(t:PShadowShape);
var
  i:integer;
  angle:integer;
begin
  if not le_CollideBB(t.bb,le_scrBB) then exit;

  if t.ShapeType=lePolyShape then
   begin
    glBegin(GL_TRIANGLE_FAN);
    glColor4f(1,1,1,1);
    for i:=0 to t.PolyData.vCount-1 do glVertex2f(t.PolyData.verts[i].x, t.PolyData.verts[i].y);
    glEnd;
   end;

  if t.ShapeType=leCircleShape then
   begin
    glBegin(GL_TRIANGLE_FAN);
    glColor4f(1,1,1,1);
    glVertex2f(t.position.x,t.position.y);

    for angle:=0 to le_CircleSubdivisions-1 do
    begin
     glVertex2f( t.CircleData.radius*le_CircleSinCos[angle].y + t.position.x, t.CircleData.radius*le_CircleSinCos[angle].x + t.position.y);
    end;

    glVertex2f(t.CircleData.radius+t.position.x, t.position.y);
    glEnd();
   end;
end;

procedure le_FreeShadowShape(t:PShadowShape);
begin
  DataList_Free( le_Shapes, DataList_GetID( le_Shapes, t ) );
  if t.ShapeType=leCircleShape then dispose(t.CircleData);
  if t.ShapeType=lePolyShape then dispose(t.PolyData);
  Dispose(t);
end;

procedure le_EachShadowShape( p: dl_proc );
begin
  DataList_Each( le_Shapes, p );
end;

function le_GetShadowVerts(l:PLightSource;s:PShadowShape ): le_ShadowDrawData;
var
  CurrentPoint,NextPoint:integer;
  v1,v2:leVect;
  bpf:integer;
  Edges: array of integer;
  k:integer;
  Error:boolean;
  lk:single;
  i:integer;
  ltv,ac1v,bc: leVect;
  ac,ac1: single;
begin
  Error:=false;

  if s.ShapeType=lePolyShape then
  begin
   bpf:=0;
   SetLength(Edges,s.PolyData.vCount);

   for CurrentPoint:=0 to s.PolyData.vCount-1 do
   begin
    NextPoint:=CurrentPoint+1;
    if CurrentPoint=s.PolyData.vCount-1 then NextPoint:=0;
    v1 := le_vPerp(le_VSub(s.PolyData.Verts[NextPoint],s.PolyData.Verts[CurrentPoint]));
    v2 := le_VSub(l.position,s.PolyData.Verts[CurrentPoint]);
    if le_vScale(v1, v2) > 0 then
    Edges[CurrentPoint]:=1 else Edges[CurrentPoint]:=-1;
   end;

   for CurrentPoint:=0 to s.PolyData.vCount-1 do
   begin
    NextPoint:=CurrentPoint+1;
    if CurrentPoint=s.PolyData.vCount-1 then NextPoint:=0;
    if Edges[CurrentPoint]*Edges[NextPoint]<0 then
    begin
     inc(bpf);
     if (bpf=1) or (bpf=2) then
     begin
      result.verts[bpf-1]:=s.PolyData.Verts[NextPoint];
      if (l.pRadius>0) and (le_SoftShadows) then
      begin
       ltv:=le_vSub(result.verts[bpf-1],l.position);
       ac:=le_vLength(ltv);
       ac1:=l.radius-ac;
       lk:=ac1/ac;
       ac1v:=le_vNormalize(ltv);
       bc:=le_vPerp(ac1v);
       if Edges[CurrentPoint]=1 then bc:=le_vMult(bc,l.pRadius*lk) else bc:=le_vMult(bc,l.pRadius*(-lk));
       ac1v:=le_vMult(ac1v,l.radius);

       result.pverts[bpf-1,2]:=result.verts[bpf-1];
       result.pverts[bpf-1,1]:=le_vAdd(result.verts[bpf-1],ac1v);
       result.pverts[bpf-1,0]:=le_vAdd(result.pverts[bpf-1,1],bc);

       result.pverts[bpf-1,0]:=le_vToLight(result.pverts[bpf-1,0],l);
       result.pverts[bpf-1,1]:=le_vToLight(result.pverts[bpf-1,1],l);
       result.pverts[bpf-1,2]:=le_vToLight(result.pverts[bpf-1,2],l);
      end;
     end;
    end;
   end;

  if bpf<>2 then Error:=true;
  end;

  if s.ShapeType=leCircleShape then
  begin
   v1.x := sqrt(sqr(s.Position.X - l.position.X) + sqr(s.Position.Y - l.position.Y));
   if v1.x > s.CircleData.radius  then
    begin
     v1.y := sqrt(sqr(v1.x)-sqr(s.CircleData.radius));
     v2.x := arcsin((s.Position.x - l.position.x) / v1.x);
     v2.y := arcsin(s.CircleData.radius / v1.x);

     if s.Position.y < l.position.y then k := -1 else k := 1;

     result.verts[0].x := trunc(l.position.x +     v1.y * sin(v2.x - v2.y));
     result.verts[0].y := trunc(l.position.y + k * v1.y * cos(v2.x - v2.y));

     result.verts[1].x := trunc(l.position.x +     v1.y * sin(v2.x + v2.y));
     result.verts[1].y := trunc(l.position.y + k * v1.y * cos(v2.x + v2.y));

     if (l.pRadius>0) and (le_SoftShadows) then for i:=0 to 1 do
     begin
      ltv:=le_vSub(result.verts[i],l.position);
      ac:=le_vLength(ltv);
      ac1:=l.radius-ac;
      lk:=ac1/ac;
      ac1v:=le_vNormalize(ltv);
      bc:=le_vPerp(ac1v);
      if i=1 then k:=-k;
      bc:=le_vMult(bc,l.pRadius*lk*k);
      ac1v:=le_vMult(ac1v,l.radius);

      result.pverts[i,2]:=result.verts[i];
      result.pverts[i,1]:=le_vAdd(result.verts[i],ac1v);
      result.pverts[i,0]:=le_vAdd(result.pverts[i,1],bc);
      result.pverts[i,0]:=le_vToLight(result.pverts[i,0],l);
      result.pverts[i,1]:=le_vToLight(result.pverts[i,1],l);
      result.pverts[i,2]:=le_vToLight(result.pverts[i,2],l);
    end;
   end else Error:=true;
  end;

  if Error=true then
  begin
   l.update:=false;
   exit;
  end;

  result.verts[2]:=le_vSub(result.verts[0],l.position);
  result.verts[2]:=le_vAdd(le_vMult(result.verts[2],l.radius*10),l.position);

  result.verts[3]:=le_vSub(result.verts[1],l.position);
  result.verts[3]:=le_vAdd(le_vMult(result.verts[3],l.radius*10),l.position);

  result.verts[0]:=le_vToLight(result.verts[0],l);
  result.verts[1]:=le_vToLight(result.verts[1],l);

  result.verts[2]:=le_vToLight(result.verts[2],l);
  result.verts[3]:=le_vToLight(result.verts[3],l);

  result.group:=s.Group;
end;

procedure le_DrawShadows(t:PLightSource);
var
  i: integer;
begin
  glDepthFunc(GL_NOTEQUAL);

  if (le_SoftShadows) and (t.pRadius>0) then
  begin
   glEnable( GL_BLEND );
   glEnable( GL_TEXTURE_2D );
   glBindTexture( GL_TEXTURE_2D, le_PenTex.ID );
   glBegin(GL_TRIANGLES);
   glColor4f(1,1,1, t.intensity );
   for i:=0 to t.ShapesCount-1 do
   begin
    glTexCoord2f(0,0);
    glVertex3f(t.Shadows[i].pverts[0,0].x,t.Shadows[i].pverts[0,0].y,t.Shadows[i].group);
    glTexCoord2f(0,1);
    glVertex3f(t.Shadows[i].pverts[0,1].x,t.Shadows[i].pverts[0,1].y,t.Shadows[i].group);
    glTexCoord2f(1,0);
    glVertex3f(t.Shadows[i].pverts[0,2].x,t.Shadows[i].pverts[0,2].y,t.Shadows[i].group);

    glTexCoord2f(0,0);
    glVertex3f(t.Shadows[i].pverts[1,0].x,t.Shadows[i].pverts[1,0].y,t.Shadows[i].group);
    glTexCoord2f(0,1);
    glVertex3f(t.Shadows[i].pverts[1,1].x,t.Shadows[i].pverts[1,1].y,t.Shadows[i].group);
    glTexCoord2f(1,0);
    glVertex3f(t.Shadows[i].pverts[1,2].x,t.Shadows[i].pverts[1,2].y,t.Shadows[i].group);
   end;
   glEnd;
   glDisable( GL_TEXTURE_2D );
   glDisable( GL_BLEND );
  end;

  glBegin(GL_QUADS);
  glColor4f(0,0,0,1);

  for i:=0 to t.ShapesCount-1 do
  begin
   glVertex3f(t.Shadows[i].verts[0].x,t.Shadows[i].verts[0].y,t.Shadows[i].group);
   glVertex3f(t.Shadows[i].verts[2].x,t.Shadows[i].verts[2].y,t.Shadows[i].group);
   glVertex3f(t.Shadows[i].verts[3].x,t.Shadows[i].verts[3].y,t.Shadows[i].group);
   glVertex3f(t.Shadows[i].verts[1].x,t.Shadows[i].verts[1].y,t.Shadows[i].group);
  end;
  glEnd;
end;

procedure le_DrawLightSource(t:PLightSource);
var
  angle:integer;
  a,SubA:single;
  rSub:integer;
begin
  if t.sAngle=0 then
  begin
   glEnable(GL_BLEND);
   glBegin(GL_TRIANGLE_FAN);
   glColor4f(t.color.r, t.color.g, t.color.b, t.intensity);
   glVertex2f(t.res/2,t.res/2);
   glColor4f(0, 0, 0, 0 );

   for angle:=0 to le_LightSubdivisions-1 do
    begin
     glVertex2f( t.radius*le_LightSinCos[angle].y + t.res/2, t.radius*le_LightSinCos[angle].x + t.res/2);
    end;

   glVertex2f(t.res/2 + t.radius, t.res/2);
   glEnd();
   glDisable(GL_BLEND);
  end else
  begin    //todo: optimize this
   glEnable(GL_BLEND);
   glBegin(GL_TRIANGLE_FAN);
   glColor4f(t.color.r, t.color.g, t.color.b, t.intensity);
   glVertex2f(t.res/2,t.res/2);
   glColor4f(0, 0, 0, 0 );

   SubA:=pi*2/le_LightSubdivisions;
   rSub:=round(t.sAngle*2/SubA);

   for angle:=1 to rSub do
   begin
    a:=-t.sAngle+t.Angle+SubA*angle;
    glVertex2f( t.radius*cos(a) + t.res/2, t.radius*sin(a) + t.res/2);
   end;

   glVertex2f(t.res/2,t.res/2);
   glEnd();
   glDisable( GL_BLEND );

   if (le_SoftShadows) and (t.sSoft>0) then
   begin
    glEnable( GL_BLEND );
    glEnable( GL_TEXTURE_2D );
    glBindTexture( GL_TEXTURE_2D, le_lPenTex.ID );
    glBegin(GL_TRIANGLES);

    glColor4f(0, 0, 0, 0 );

    glTexCoord2f(0,0);
    glVertex2f( t.radius*cos(a+t.sSoft) + t.res/2, t.radius*sin(a+t.sSoft) + t.res/2);
    glTexCoord2f(0,1);
    glVertex2f(t.radius*cos(a) + t.res/2, t.radius*sin(a) + t.res/2);
    glColor4f(1,1,1, t.intensity );
    glTexCoord2f(1,0);
    glVertex2f(t.res/2,t.res/2);

    glColor4f(0, 0, 0, 0 );
    glTexCoord2f(0,0);
    glVertex2f( t.radius*cos(-t.sAngle+t.Angle+SubA-t.sSoft) + t.res/2, t.radius*sin(-t.sAngle+t.Angle+SubA-t.sSoft) + t.res/2);
    glTexCoord2f(0,1);
    glVertex2f(t.radius*cos(-t.sAngle+t.Angle+SubA) + t.res/2, t.radius*sin(-t.sAngle+t.Angle+SubA) + t.res/2);
    glColor4f(1,1,1, t.intensity );
    glTexCoord2f(1,0);
    glVertex2f(t.res/2,t.res/2);

    glEnd;
    glDisable( GL_TEXTURE_2D );
    glDisable( GL_BLEND );
    end;
  end;
end;

procedure le_zDrawShadowShape(t:PShadowShape;l:PlightSource);
var
  i:integer;
  angle: integer;
  pLoc:leVect;
begin
  if t.ShapeType=lePolyShape then
   begin
    glBegin(GL_TRIANGLE_FAN);
    glColor4f(0,0,0,0);
    for i:=0 to t.PolyData.vCount-1 do
    begin
    pLoc:=le_vToLight(t.PolyData.verts[i],l);
    glVertex3f(pLoc.x,pLoc.y,t.Group);
    end;
    glEnd;
   end;

  if t.ShapeType=leCircleShape then
   begin
    pLoc:=le_vToLight(t.position,l);

    glBegin(GL_TRIANGLE_FAN);
    glColor4f(0,0,0,0);
    glVertex3f(pLoc.x,pLoc.y,t.Group);

    for angle:=0 to le_CircleSubdivisions-1 do
    begin
     glVertex3f( t.CircleData.radius*le_CircleSinCos[angle].y + pLoc.x, t.CircleData.radius*le_CircleSinCos[angle].x + pLoc.y,t.Group);
    end;

    glVertex3f(pLoc.x+t.CircleData.radius, pLoc.y,t.Group);
    glEnd();
   end;
end;

function le_UpdateBB(v:array of leVect;vn:integer):le_BB; overload;
var
  i:integer;
begin
  result.lt:=v[0];
  result.rb:=v[0];
  for i:=1 to vn-1 do
  begin
   if v[i].x<result.lt.x then result.lt.x:=v[i].x;
   if v[i].y<result.lt.y then result.lt.y:=v[i].y;
   if v[i].x>result.rb.x then result.rb.x:=v[i].x;
   if v[i].y>result.rb.y then result.rb.y:=v[i].y;
  end;
end;

function le_UpdateBB(p:leVect;r:single):le_BB; overload;
begin
  result.lt:=le_v(p.x-r,p.y-r);
  result.rb:=le_v(p.x+r,p.y+r);
end;

procedure le_DrawBB(bb:le_BB);
begin
  PR2D_Line(bb.lt.x,bb.lt.y, bb.rb.x,bb.lt.y,$FFFFFF);
  PR2D_Line(bb.lt.x,bb.rb.y, bb.rb.x,bb.rb.y,$FFFFFF);
  PR2D_Line(bb.lt.x,bb.lt.y, bb.lt.x,bb.rb.y,$FFFFFF);
  PR2D_Line(bb.rb.x,bb.lt.y, bb.rb.x,bb.rb.y,$FFFFFF);
end;

procedure le_DrawShapeBB(t: PShadowShape);
begin
  le_DrawBB(t.bb);
end;

procedure le_DrawLightBB(t: PLightSource);
begin
  le_DrawBB(t.bb);
end;

function le_CollideBB(bb1,bb2:le_BB):boolean;
begin
  result:=false;
  if ((bb1.rb.y>=bb2.lt.y) and (bb1.lt.y<=bb2.rb.y) and (bb1.rb.x>=bb2.lt.x) and (bb1.lt.x<=bb2.rb.x)) then result:=true;
end;

end.

