//
// This unit is part of the GLScene Engine, http://glscene.org
//

unit GLLensFlare;

(* Lens flare object. *)

interface

{$I GLScene.inc}

uses
  Winapi.OpenGL,
  Winapi.OpenGLext,
  System.Classes,
  System.SysUtils,
  System.Math,
  
  OpenGLTokens,
  GLScene,
  GLPersistentClasses,
  GLPipelineTransformation,
  GLVectorGeometry,
  GLObjects,
  GLContext,
  GLColor,
  GLBaseClasses,
  GLRenderContextInfo,
  GLState,
  GLVectorTypes,
  GLS.Utils,
  GLTextureFormat;

type

  TFlareElement = (feGlow, feRing, feStreaks, feRays, feSecondaries);
  TFlareElements = set of TFlareElement;

  (* The actual gradients between two colors are, of course, calculated by OpenGL. 
     The start and end colors of a gradient are stored to represent the color of
     lens flare elements. *)
  TGLFlareGradient = class(TGLUpdateAbleObject)
  private
    FFromColor: TGLColor;
    FToColor: TGLColor;
  protected
    procedure SetFromColor(const val: TGLColor);
    procedure SetToColor(const val: TGLColor);
  public
    constructor Create(AOwner: TPersistent); override;
    constructor CreateInitialized(AOwner: TPersistent;
      const fromColor, toColor: TColorVector);
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;
  published
    property FromColor: TGLColor read FFromColor write SetFromColor;
    property ToColor: TGLColor read FToColor write SetToColor;
  end;

const
  cDefaultFlareElements = [feGlow, feRing, feStreaks, feRays, feSecondaries];

type

  TGLLensFlare = class(TGLBaseSceneObject)
  private
    FSize: Integer;
    FDeltaTime: Single;
    FCurrSize: Single;
    FSeed: Integer;
    FSqueeze: Single;
    FNumStreaks: Integer;
    FStreakWidth, FStreakAngle: Single;
    FNumSecs: Integer;
    FResolution: Integer;
    FAutoZTest: Boolean;
    FElements: TFlareElements;
    FSin20Res, FCos20Res: array of Single;
    FSinRes, FCosRes: array of Single;
    FTexRays: TGLTextureHandle;
    FFlareIsNotOccluded: Boolean;
    FOcclusionQuery: TGLOcclusionQueryHandle;
    FGlowGradient: TGLFlareGradient;
    FRingGradient: TGLFlareGradient;
    FStreaksGradient: TGLFlareGradient;
    FRaysGradient: TGLFlareGradient;
    FSecondariesGradient: TGLFlareGradient;
    FDynamic: Boolean;
    FPreRenderPoint: TGLRenderPoint;
  protected
    procedure SetGlowGradient(const val: TGLFlareGradient);
    procedure SetRingGradient(const val: TGLFlareGradient);
    procedure SetStreaksGradient(const val: TGLFlareGradient);
    procedure SetRaysGradient(const val: TGLFlareGradient);
    procedure SetSecondariesGradient(const val: TGLFlareGradient);
    procedure SetSize(aValue: Integer);
    procedure SetSeed(aValue: Integer);
    procedure SetSqueeze(aValue: Single);
    function StoreSqueeze: Boolean;
    procedure SetNumStreaks(aValue: Integer);
    procedure SetStreakWidth(aValue: Single);
    function StoreStreakWidth: Boolean;
    procedure SetStreakAngle(aValue: Single);
    procedure SetNumSecs(aValue: Integer);
    procedure SetResolution(aValue: Integer);
    procedure SetAutoZTest(aValue: Boolean);
    procedure SetElements(aValue: TFlareElements);
    procedure SetDynamic(aValue: Boolean);
    procedure SetPreRenderPoint(const val: TGLRenderPoint);
    procedure PreRenderEvent(Sender: TObject; var rci: TGLRenderContextInfo);
    procedure PreRenderPointFreed(Sender: TObject);
    (* These are quite unusual in that they don't use an RCI, since
     PreRender is done before proper rendering starts, but we do know
     which RC is being used, so we can use this state cache *)
    procedure SetupRenderingOptions(StateCache: TGLStateCache);
    procedure RenderRays(StateCache: TGLStateCache; const size: Single);
    procedure RenderStreaks(StateCache: TGLStateCache);
    procedure RenderRing;
    procedure RenderSecondaries(const posVector: TAffineVector);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    procedure BuildList(var rci: TGLRenderContextInfo); override;
    procedure DoProgress(const progressTime: TGLProgressTimes); override;
    (* Prepares pre-rendered texture to speed up actual rendering.
       Will use the currently active context as scratch space, and will
       automatically do nothing if things have already been prepared,
       thus you can invoke it systematically in a Viewer.BeforeRender
       event f.i. *)
    procedure PreRender(activeBuffer: TGLSceneBuffer);
    (* Access to the Flare's current size.
       Flares decay or grow back over several frames, depending on their
       occlusion status, and this property allows to track or manually
       alter this instantaneous size. *)
    property FlareInstantaneousSize: Single read FCurrSize write FCurrSize;
  published
    property GlowGradient: TGLFlareGradient read FGlowGradient write SetGlowGradient;
    property RingGradient: TGLFlareGradient read FRingGradient;
    property StreaksGradient: TGLFlareGradient read FStreaksGradient;
    property RaysGradient: TGLFlareGradient read FRaysGradient;
    property SecondariesGradient: TGLFlareGradient read FSecondariesGradient;
    // MaxRadius of the flare.
    property Size: Integer read FSize write SetSize default 50;
    // Random seed
    property Seed: Integer read FSeed write SetSeed;
    // To create elliptic flares.
    property Squeeze: Single read FSqueeze write SetSqueeze stored StoreSqueeze;
    // Number of streaks.
    property NumStreaks: Integer read FNumStreaks write SetNumStreaks default 4;
    // Width of the streaks.
    property StreakWidth: Single read FStreakWidth write SetStreakWidth stored
      StoreStreakWidth;
    // Angle of the streaks (in degrees)
    property StreakAngle: Single read FStreakAngle write SetStreakAngle;
    // Number of secondary flares.
    property NumSecs: Integer read FNumSecs write SetNumSecs default 8;
    // Number of segments used when rendering circles.
    property Resolution: Integer read FResolution write SetResolution default 64;
    {Automatically computes FlareIsNotOccluded depending on ZBuffer test.
       Not that the automated test may use test result from the previous
       frame into the next (to avoid a rendering stall). }
    property AutoZTest: Boolean read FAutoZTest write SetAutoZTest default True;
    {Is the LensFlare not occluded?.
       If false the flare will fade away, if true, it will fade in and stay.
       This value is automatically updated if AutoZTest is set. }
    property FlareIsNotOccluded: Boolean read FFlareIsNotOccluded write
      FFlareIsNotOccluded;
    // Which elements should be rendered?
    property Elements: TFlareElements read FElements write SetElements default
      cDefaultFlareElements;
    (* Is the flare size adjusted dynamically?
       If true, the flare size will be grown and reduced over a few frames
       when it switches between occluded and non-occluded states. This
       requires animation to be active, but results in a smoother appearance.
       When false, flare will either be at full size or hidden.
       The flare is always considered non-dynamic at design-time. *)
    property Dynamic: Boolean read FDynamic write FDynamic default True;

    (* PreRender point for pre-rendered flare textures. 
       See PreRender method for more details. *)
    property PreRenderPoint: TGLRenderPoint read FPreRenderPoint write
      SetPreRenderPoint;
    property ObjectsSorting;
    property Position;
    property Visible;
    property OnProgress;
    property Behaviours;
    property Effects;
  end;

// ------------------------------------------------------------------
implementation
// ------------------------------------------------------------------

// ------------------
// ------------------ TGLFlareGradient ------------------
// ------------------

constructor TGLFlareGradient.Create(AOwner: TPersistent);
begin
  inherited;
  FFromColor := TGLColor.Create(Self);
  FToColor := TGLColor.Create(Self);
end;

constructor TGLFlareGradient.CreateInitialized(AOwner: TPersistent;
  const fromColor, toColor: TColorVector);
begin
  Create(AOwner);
  FFromColor.Initialize(fromColor);
  FToColor.Initialize(toColor);
end;

destructor TGLFlareGradient.Destroy;
begin
  FToColor.Free;
  FFromColor.Free;
  inherited;
end;

procedure TGLFlareGradient.Assign(Source: TPersistent);
begin
  if Source is TGLFlareGradient then
  begin
    FromColor := TGLFlareGradient(Source).FromColor;
    ToColor := TGLFlareGradient(Source).ToColor;
  end;
  inherited;
end;

procedure TGLFlareGradient.SetFromColor(const val: TGLColor);
begin
  FFromColor.Assign(val);
end;

procedure TGLFlareGradient.SetToColor(const val: TGLColor);
begin
  FToColor.Assign(val);
end;

// ------------------
// ------------------ TGLLensFlare ------------------
// ------------------

constructor TGLLensFlare.Create(AOwner: TComponent);
begin
  inherited;
  // Set default parameters:
  ObjectStyle := ObjectStyle + [osDirectDraw, osNoVisibilityCulling];
  FSize := 50;
  FSeed := 1465;
  FSqueeze := 1;
  FNumStreaks := 4;
  FStreakWidth := 2;
  FNumSecs := 8;
  FAutoZTest := True;
  FlareIsNotOccluded := True;
  FDynamic := True;
  SetResolution(64);
  // Render all elements by default.
  FElements := [feGlow, feRing, feStreaks, feRays, feSecondaries];
  // Setup default gradients:
  FGlowGradient := TGLFlareGradient.CreateInitialized(Self,
    VectorMake(1, 1, 0.8, 0.3), VectorMake(1, 0.2, 0, 0));
  FRingGradient := TGLFlareGradient.CreateInitialized(Self,
    VectorMake(0.5, 0.2, 0, 0.1), VectorMake(0.5, 0.4, 0, 0.1));
  FStreaksGradient := TGLFlareGradient.CreateInitialized(Self,
    VectorMake(1, 1, 1, 0.2), VectorMake(0.2, 0, 1, 0));
  FRaysGradient := TGLFlareGradient.CreateInitialized(Self,
    VectorMake(1, 0.8, 0.5, 0.05), VectorMake(0.5, 0.2, 0, 0));
  FSecondariesGradient := TGLFlareGradient.CreateInitialized(Self,
    VectorMake(0, 0.2, 1, 0), VectorMake(0, 0.8, 0.2, 0.15));

  FTexRays := TGLTextureHandle.Create;
end;

destructor TGLLensFlare.Destroy;
begin
  PreRenderPoint := nil;
  FGlowGradient.Free;
  FRingGradient.Free;
  FStreaksGradient.Free;
  FRaysGradient.Free;
  FSecondariesGradient.Free;
  FOcclusionQuery.Free;
  FTexRays.Free;
  inherited;
end;

procedure TGLLensFlare.Notification(AComponent: TComponent; Operation:
  TOperation);
begin
  if (Operation = opRemove) and (AComponent = FPreRenderPoint) then
    PreRenderPoint := nil;
  inherited;
end;

procedure TGLLensFlare.SetupRenderingOptions(StateCache: TGLStateCache);
begin
  with StateCache do
  begin
    Disable(stLighting);
    Disable(stDepthTest);
    Disable(stFog);
    Disable(stColorMaterial);
    Disable(stCullFace);
    DepthWriteMask := False;
    Enable(stBlend);
    SetBlendFunc(bfSrcAlpha, bfOne);
    Disable(stAlphaTest);
    PolygonMode := pmFill;
  end;
end;

procedure TGLLensFlare.RenderRays(StateCache: TGLStateCache; const size:
  Single);
var
  i: Integer;
  rnd: Single;
begin
{$IFDEF USE_OPENGL_DEBUG}
  if gl.GREMEDY_string_marker then
    gl.StringMarkerGREMEDY(14, 'LensFlare.Rays');
{$ENDIF}

  with StateCache do
  begin
    LineWidth := 1;
    Disable(stLineSmooth);
    Disable(stLineStipple);
  end;

  gl.Begin_(GL_LINES);
  for i := 0 to Resolution * 20 - 1 do
  begin
    if (i and 1) <> 0 then
      rnd := 1.5 * Random * size
    else
      rnd := Random * size;
    gl.Color4fv(RaysGradient.FromColor.AsAddress);
    gl.Vertex2f(0, 0);
    gl.Color4fv(RaysGradient.ToColor.AsAddress);
    gl.Vertex2f(rnd * FCos20Res[i], rnd * FSin20Res[i] * Squeeze);
  end;
  gl.End_;
end;

procedure TGLLensFlare.RenderStreaks(StateCache: TGLStateCache);
var
  i: Integer;
  a, f, s, c: Single;
begin
{$IFDEF USE_OPENGL_DEBUG}
  if gl.GREMEDY_string_marker then
    gl.StringMarkerGREMEDY(17, 'LensFlare.Streaks');
{$ENDIF}
  StateCache.Enable(stLineSmooth);
  StateCache.LineWidth := StreakWidth;
  a := c2PI / NumStreaks;
  f := 1.5 * FCurrSize;
  gl.Begin_(GL_LINES);
  for i := 0 to NumStreaks - 1 do
  begin
    SinCosine(StreakAngle * cPIdiv180 + a * i, f, s, c);
    gl.Color4fv(StreaksGradient.FromColor.AsAddress);
    gl.Vertex3fv(@NullVector);
    gl.Color4fv(StreaksGradient.ToColor.AsAddress);
    gl.Vertex2f(c, Squeeze * s);
  end;
  gl.End_;
  StateCache.Disable(stLineSmooth);
end;

procedure TGLLensFlare.RenderRing;
var
  i: Integer;
  rW, s0, c0, s, c: Single;
begin
{$IFDEF USE_OPENGL_DEBUG}
  if GL.GREMEDY_string_marker then
    GL.StringMarkerGREMEDY(14, 'LensFlare.Ring');
{$ENDIF}
  rW := FCurrSize * (1 / 15); // Ring width
  gl.Begin_(GL_QUADS);
  s0 := 0;
  c0 := 0.6;
  for i := 0 to Resolution - 1 do
  begin
    s := s0;
    c := c0;
    s0 := FSinRes[i] * 0.6 * Squeeze;
    c0 := FCosRes[i] * 0.6;

    gl.Color4fv(GlowGradient.ToColor.AsAddress);
    gl.Vertex2f((FCurrSize - rW) * c, (FCurrSize - rW) * s);
    gl.Color4fv(RingGradient.FromColor.AsAddress);
    gl.Vertex2f(FCurrSize * c, Squeeze * FCurrSize * s);

    gl.Vertex2f(FCurrSize * c0, FCurrSize * s0);
    gl.Color4fv(GlowGradient.ToColor.AsAddress);
    gl.Vertex2f((FCurrSize - rW) * c0, (FCurrSize - rW) * s0);

    gl.Color4fv(RingGradient.FromColor.AsAddress);
    gl.Vertex2f(FCurrSize * c, FCurrSize * s);
    gl.Vertex2f(FCurrSize * c0, FCurrSize * s0);

    gl.Color4fv(GlowGradient.ToColor.AsAddress);
    gl.Vertex2f((FCurrSize + rW) * c0, (FCurrSize + rW) * s0);
    gl.Vertex2f((FCurrSize + rW) * c, (FCurrSize + rW) * s);
  end;
  gl.End_;
end;

procedure TGLLensFlare.RenderSecondaries(const posVector: TAffineVector);
var
  i, j: Integer;
  rnd: Single;
  v: TAffineVector;
  grad: TGLFlareGradient;
begin
{$IFDEF USE_OPENGL_DEBUG}
  if GL.GREMEDY_string_marker then
    GL.StringMarkerGREMEDY(21, 'LensFlare.Secondaries');
{$ENDIF}
  // Other secondaries (plain gradiented circles, like the glow):
  for j := 1 to NumSecs do
  begin
    rnd := 2 * Random - 1;
    // If rnd < 0 then the secondary glow will end up on the other side
    // of the origin. In this case, we can push it really far away from
    // the flare. If  the secondary is on the flare's side, we pull it
    // slightly towards the origin to avoid it winding up in the middle
    // of the flare.
    if rnd < 0 then
      v := VectorScale(posVector, rnd)
    else
      v := VectorScale(posVector, 0.8 * rnd);
    if j mod 3 = 0 then
      grad := GlowGradient
    else
      grad := SecondariesGradient;
    rnd := (Random + 0.1) * FCurrSize * 0.25;

    gl.Begin_(GL_TRIANGLE_FAN);
    gl.Color4fv(grad.FromColor.AsAddress);
    gl.Vertex2f(v.X, v.Y);
    gl.Color4fv(grad.ToColor.AsAddress);
    for i := 0 to Resolution - 1 do
      gl.Vertex2f(FCosRes[i] * rnd + v.X, FSinRes[i] * rnd + v.Y);
    gl.End_;
  end;
end;

procedure TGLLensFlare.BuildList(var rci: TGLRenderContextInfo);
var
  i: Integer;
  depth, dist: Single;
  posVector, v, rv: TAffineVector;
  screenPos: TAffineVector;
  flareInViewPort, dynamicSize: Boolean;
  oldSeed: LongInt;
  projMatrix: TMatrix;
  CurrentBuffer: TGLSceneBuffer;
begin
  if (rci.drawState = dsPicking) then
  begin
    if Count <> 0 then
      Self.RenderChildren(0, Count - 1, rci);
    Exit;
  end;
  CurrentBuffer := TGLSceneBuffer(rci.buffer);

  SetVector(v, AbsolutePosition);
  // are we looking towards the flare?
  rv := VectorSubtract(v, PAffineVector(@rci.cameraPosition)^);
  if VectorDotProduct(rci.cameraDirection, rv) > 0 then
  begin
    // find out where it is on the screen.
    screenPos := CurrentBuffer.WorldToScreen(v);
    flareInViewPort := (screenPos.X < rci.viewPortSize.cx)
    and (screenPos.X >= 0)
    and (screenPos.Y < rci.viewPortSize.cy)
    and (screenPos.Y >= 0);
  end
  else
    flareInViewPort := False;

  dynamicSize := FDynamic and not (csDesigning in ComponentState);
  if dynamicSize then
  begin
    // make the glow appear/disappear progressively
    if flareInViewPort and FlareIsNotOccluded then
    begin
      FCurrSize := FCurrSize + FDeltaTime * 10 * Size;
      if FCurrSize > Size then
        FCurrSize := Size;
    end
    else
    begin
      FCurrSize := FCurrSize - FDeltaTime * 10 * Size;
      if FCurrSize < 0 then
        FCurrSize := 0;
    end;
  end
  else
  begin
    if flareInViewPort and FlareIsNotOccluded then
      FCurrSize := Size
    else
      FCurrSize := 0;
  end;

  // Prepare matrices
  gl.PushMatrix;
  gl.LoadMatrixf(@CurrentBuffer.BaseProjectionMatrix);

  gl.MatrixMode(GL_PROJECTION);
  gl.PushMatrix;
  projMatrix := IdentityHmgMatrix;
  projMatrix.V[0].X := 2 / rci.viewPortSize.cx;
  projMatrix.V[1].Y := 2 / rci.viewPortSize.cy;
  gl.LoadMatrixf(@projMatrix);

  MakeVector(posVector,
    screenPos.X - rci.viewPortSize.cx * 0.5,
    screenPos.Y - rci.viewPortSize.cy * 0.5,
    0);

  if AutoZTest then
  begin
    if dynamicSize and (GL.HP_occlusion_test or
      TGLOcclusionQueryHandle.IsSupported) then
    begin
      // hardware-based occlusion test is possible
      FlareIsNotOccluded := True;

      rci.GLStates.SetColorMask([]);
      rci.GLStates.Disable(stAlphaTest);
      rci.GLStates.DepthWriteMask := False;
      rci.GLStates.Enable(stDepthTest);
      rci.GLStates.DepthFunc := cfLEqual;

      if TGLOcclusionQueryHandle.IsSupported then
      begin
        // preferred method, doesn't stall rendering too badly
        if not Assigned(FOcclusionQuery) then
          FOcclusionQuery := TGLOcclusionQueryHandle.Create;
        FOcclusionQuery.AllocateHandle;
        if FOcclusionQuery.IsDataNeedUpdate then
          FOcclusionQuery.NotifyDataUpdated
        else
          FlareIsNotOccluded := (FOcclusionQuery.PixelCount <> 0);
        FOcclusionQuery.BeginQuery;
      end
      else
      begin
        // occlusion_test, stalls rendering a bit
        gl.Enable(GL_OCCLUSION_TEST_HP);
      end;

      gl.Begin_(GL_QUADS);
      gl.Vertex3f(posVector.X + 2, posVector.Y, 1);
      gl.Vertex3f(posVector.X, posVector.Y + 2, 1);
      gl.Vertex3f(posVector.X - 2, posVector.Y, 1);
      gl.Vertex3f(posVector.X, posVector.Y - 2, 1);
      gl.End_;

      if TGLOcclusionQueryHandle.IsSupported then
        FOcclusionQuery.EndQuery
      else
      begin
        gl.Disable(GL_OCCLUSION_TEST_HP);
        gl.GetBooleanv(GL_OCCLUSION_TEST_RESULT_HP, @FFlareIsNotOccluded)
      end;

      rci.GLStates.DepthFunc := cfLEqual;
      rci.GLStates.SetColorMask(cAllColorComponents);
    end
    else
    begin
      //Compares the distance to the lensflare, to the z-buffer depth.
      //This prevents the flare from being occluded by objects BEHIND the light.
      depth := CurrentBuffer.PixelToDistance(Round(ScreenPos.X),
        Round(rci.viewPortSize.cy - ScreenPos.Y));
      dist := VectorDistance(rci.cameraPosition, self.AbsolutePosition);
      FlareIsNotOccluded := ((dist - depth) < 1);
    end;
  end;

  if FCurrSize >= 0 then
  begin

    // Random seed must be backed up, could be used for other purposes
    // (otherwise we essentially reset the random generator at each frame)
    oldSeed := RandSeed;
    RandSeed := Seed;

    SetupRenderingOptions(rci.GLStates);

    if [feGlow, feStreaks, feRays, feRing] * Elements <> [] then
    begin
      gl.Translatef(posVector.X, posVector.Y, posVector.Z);

      // Glow (a circle with transparent edges):
      if feGlow in Elements then
      begin
        gl.Begin_(GL_TRIANGLE_FAN);
        gl.Color4fv(GlowGradient.FromColor.AsAddress);
        gl.Vertex2f(0, 0);
        gl.Color4fv(GlowGradient.ToColor.AsAddress);
        for i := 0 to Resolution - 1 do
          gl.Vertex2f(FCurrSize * FCosRes[i],
            Squeeze * FCurrSize * FSinRes[i]);
        gl.End_;
      end;

      if feStreaks in Elements then
        RenderStreaks(rci.GLStates);

      // Rays (random-length lines from the origin):
      if feRays in Elements then
      begin
        if FTexRays.Handle <> 0 then
        begin
        {$IFDEF USE_OPENGL_DEBUG}
          if GL.GREMEDY_string_marker then
            GL.StringMarkerGREMEDY(19, 'LensFlare.RaysQuad');
        {$ENDIF}
          rci.GLStates.TextureBinding[0, ttTexture2D] := FTexRays.Handle;
          rci.GLStates.ActiveTextureEnabled[ttTexture2D] := True;
          gl.TexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

          gl.Begin_(GL_QUADS);
          gl.TexCoord2f(0, 0);
          gl.Vertex2f(-FCurrSize, -FCurrSize);
          gl.TexCoord2f(1, 0);
          gl.Vertex2f(FCurrSize, -FCurrSize);
          gl.TexCoord2f(1, 1);
          gl.Vertex2f(FCurrSize, FCurrSize);
          gl.TexCoord2f(0, 1);
          gl.Vertex2f(-FCurrSize, FCurrSize);
          gl.End_;

          rci.GLStates.ActiveTextureEnabled[ttTexture2D] := False;
        end
        else
          RenderRays(rci.GLStates, FCurrSize);
      end;

      if feRing in Elements then
        RenderRing;

      gl.LoadMatrixf(@projMatrix);
    end;

    if feSecondaries in Elements then
      RenderSecondaries(posVector);

    RandSeed := oldSeed;
  end;

  gl.PopMatrix;
  gl.MatrixMode(GL_MODELVIEW);
  gl.PopMatrix;

  if Count > 0 then
    Self.RenderChildren(0, Count - 1, rci);
end;

procedure TGLLensFlare.DoProgress(const progressTime: TGLProgressTimes);
begin
  inherited;
  FDeltaTime := progressTime.deltaTime;
end;

procedure TGLLensFlare.PreRender(activeBuffer: TGLSceneBuffer);
var
  i, texSize, maxSize: Integer;
  stateCache: TGLStateCache;
begin
  if FTexRays.Handle <> 0 then
    Exit;
  with activeBuffer.RenderingContext do
  begin
    stateCache := GLStates;
    PipelineTransformation.Push;
    PipelineTransformation.SetProjectionMatrix(CreateOrthoMatrix(0, activeBuffer.Width, 0, activeBuffer.Height, -1, 1));
    PipelineTransformation.SetViewMatrix(IdentityHmgMatrix);
  end;
  SetupRenderingOptions(stateCache);

  texSize := RoundUpToPowerOf2(Size);
  if texSize < Size * 1.5 then
    texSize := texSize * 2;
  gl.GetIntegerv(GL_MAX_TEXTURE_SIZE, @maxSize);
  if texSize > maxSize then
    texSize := maxSize;

  stateCache.Disable(stBlend);
  gl.Color4f(0, 0, 0, 0);
  gl.Begin_(GL_QUADS);
  gl.Vertex2f(0, 0);
  gl.Vertex2f(texSize + 4, 0);
  gl.Vertex2f(texSize + 4, texSize + 4);
  gl.Vertex2f(0, texSize + 4);
  gl.End_;
  stateCache.Enable(stBlend);

  gl.Translatef(texSize * 0.5 + 2, texSize * 0.5 + 2, 0);
  RenderRays(stateCache, texSize * 0.5);

  FTexRays.AllocateHandle;
  stateCache.TextureBinding[0, ttTexture2D] := FTexRays.Handle;
  if gl.EXT_texture_edge_clamp then
    i := GL_CLAMP_TO_EDGE
  else
    i := GL_CLAMP;
  gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, i);
  gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, i);
  gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

  gl.CopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, texSize, texSize, 0);

  activeBuffer.RenderingContext.PipelineTransformation.Pop;

  gl.CheckError;
end;

procedure TGLLensFlare.SetGlowGradient(const val: TGLFlareGradient);
begin
  FGlowGradient.Assign(val);
  StructureChanged;
end;

procedure TGLLensFlare.SetRingGradient(const val: TGLFlareGradient);
begin
  FRingGradient.Assign(val);
  StructureChanged;
end;

procedure TGLLensFlare.SetStreaksGradient(const val: TGLFlareGradient);
begin
  FStreaksGradient.Assign(val);
  StructureChanged;
end;

procedure TGLLensFlare.SetRaysGradient(const val: TGLFlareGradient);
begin
  FRaysGradient.Assign(val);
  StructureChanged;
end;

procedure TGLLensFlare.SetSecondariesGradient(const val: TGLFlareGradient);
begin
  FSecondariesGradient.Assign(val);
  StructureChanged;
end;

procedure TGLLensFlare.SetSize(aValue: Integer);
begin
  FSize := aValue;
  StructureChanged;
end;

procedure TGLLensFlare.SetSeed(aValue: Integer);
begin
  FSeed := aValue;
  StructureChanged;
end;

procedure TGLLensFlare.SetSqueeze(aValue: Single);
begin
  FSqueeze := aValue;
  StructureChanged;
end;

function TGLLensFlare.StoreSqueeze: Boolean;
begin
  Result := (FSqueeze <> 1);
end;

procedure TGLLensFlare.SetNumStreaks(aValue: Integer);
begin
  FNumStreaks := aValue;
  StructureChanged;
end;

procedure TGLLensFlare.SetStreakWidth(aValue: Single);
begin
  FStreakWidth := aValue;
  StructureChanged;
end;

function TGLLensFlare.StoreStreakWidth: Boolean;
begin
  Result := (FStreakWidth <> 2);
end;

procedure TGLLensFlare.SetStreakAngle(aValue: Single);
begin
  FStreakAngle := aValue;
  StructureChanged;
end;

procedure TGLLensFlare.SetNumSecs(aValue: Integer);
begin
  FNumSecs := aValue;
  StructureChanged;
end;

procedure TGLLensFlare.SetResolution(aValue: Integer);
begin
  if FResolution <> aValue then
  begin
    FResolution := aValue;
    StructureChanged;
    SetLength(FSin20Res, 20 * FResolution);
    SetLength(FCos20Res, 20 * FResolution);
    PrepareSinCosCache(FSin20Res, FCos20Res, 0, 360);
    SetLength(FSinRes, FResolution);
    SetLength(FCosRes, FResolution);
    PrepareSinCosCache(FSinRes, FCosRes, 0, 360);
  end;
end;

procedure TGLLensFlare.SetAutoZTest(aValue: Boolean);
begin
  if FAutoZTest <> aValue then
  begin
    FAutoZTest := aValue;
    StructureChanged;
  end;
end;

procedure TGLLensFlare.SetElements(aValue: TFlareElements);
begin
  if FElements <> aValue then
  begin
    FElements := aValue;
    StructureChanged;
  end;
end;

procedure TGLLensFlare.SetDynamic(aValue: Boolean);
begin
  if aValue <> FDynamic then
  begin
    FDynamic := aValue;
    NotifyChange(Self);
  end;
end;

procedure TGLLensFlare.SetPreRenderPoint(const val: TGLRenderPoint);
begin
  if val <> FPreRenderPoint then
  begin
    if Assigned(FPreRenderPoint) then
      FPreRenderPoint.UnRegisterCallBack(Self.PreRenderEvent);
    FPreRenderPoint := val;
    if Assigned(FPreRenderPoint) then
      FPreRenderPoint.RegisterCallBack(Self.PreRenderEvent,
        Self.PreRenderPointFreed);
  end;
end;

procedure TGLLensFlare.PreRenderEvent(Sender: TObject; var rci:
  TGLRenderContextInfo);
begin
  PreRender(rci.buffer as TGLSceneBuffer);
end;

procedure TGLLensFlare.PreRenderPointFreed(Sender: TObject);
begin
  FPreRenderPoint := nil;
end;

// ------------------------------------------------------------------
initialization
// ------------------------------------------------------------------

  RegisterClasses([TGLLensFlare]);

end.
