{********************************************}
{ TeeChart Pro OpenGL Canvas                 }
{ Copyright (c) 1998-2020 by Steema Software }
{ All Rights Reserved                        }
{********************************************}
unit TeeGLCanvas;
{$I TeeDefs.inc}

// Turn off assertions when debug is off.
{$IFOPT D-}
{$C-}
{$ENDIF}
{$IFDEF TEEOCX}
{$C-}
{$ENDIF}

interface

uses {$IFNDEF LINUX}
     Windows,
     {$ENDIF}
     SysUtils,
     Classes,

     {$IFDEF D17}
     System.UITypes,
     {$ENDIF}

     {$IFDEF D21} // Inline Hint
     System.Math.Vectors,
     {$ENDIF}

     Graphics, Controls,
     {$IFDEF D9}
     Types,
     {$ENDIF}

     {$IFDEF LINUX}
     OpenGLLinux,
     {$ELSE}
     OpenGL2,
     {$ENDIF}

     {$IFDEF BLOCKS}
     Interfaces,
     {$ENDIF}

     TeeConst, TeCanvas;

type
  GLMat=Array[0..3] of GLFloat;

  {$IFDEF LINUX}
  PGLUQuadricObj=GLUQuadricObj;
  {$ENDIF}

var
  //  TeeOpenGLFontExtrusion : Integer=0; Removed v7: Use TGLCanvas.FontExtrusion

  TeeOpenGLFontName   : PChar=TeeMsg_DefaultEngFontName;

  TeeMaterialAmbient  : Single=1;
  TeeMaterialDiffuse  : Single=1;
  TeeMaterialSpecular : Single=1;
  TeeMaterialEmission : Single=1;

  TeeFullLightModel : GLEnum= GL_FALSE;
  TeeLightLocal     : GLEnum= GL_FALSE;
  TeeColorPlanes    : GLEnum= GL_FRONT_AND_BACK;

  TeeTextAngleY  : Integer=0;
  TeeTextAngleZ  : Integer=0;
  TeeTextWidthFactor  : Single= 1.5; //1.41;
  TeeTextHeightFactor : Single= 1.6; //1.6;

  TeeDefaultLightSpot:Integer=180;

  TeeSphereSlices:Integer = 32;
  TeeSphereStacks:Integer = 32;
  TeeCylinderStacks:Integer = 6;

  TeeSmoothQuality : GLEnum = GL_NICEST;
  TeePerspectiveQuality : GLEnum = GL_NICEST;
  TeeTextureEnvMode: GLInt = GL_MODULATE;

  TeePolygonOffset : Boolean=True;
  TeeWrapTextures : Boolean=True;

const
  TeeFontListRange  = 256-32+1;
  TeeMaxFonts       = 100;

type
  // Fonts
  TGLFontCache=record
    Offset    : Integer;
    Name      : String;
    Weight    : Integer;
    Style     : TFontStyles;
    Extrusion : Single;
    GLStyle   : TTeeFontStyle;
    Size      : Integer;
  end;

  // Textures
  TTeeTexture=record
    Graphic   : TGraphic;
    GLTexture : {$IFDEF LINUX}GLBoolean{$ELSE}GLUInt{$ENDIF};
  end;

  TGLScreenSync=(ssSystem, ssYes, ssNo);

  TGLColorDepth=(cd24Bit, cd32Bit);

  TGLColorCache=record Color:TColor; Transp:Single; end;

  TGLGetPicBits=function(AGraphic:TGraphic; var Bits:PByteArray):Boolean of object;

  TGLCachedObject=class(TTeeCachedObject)
  private
    Lists : Array of Integer;

    procedure AddList;
  public
    Destructor Destroy; override;
  end;

// Different ways to draw a GL Cube:

{.$DEFINE TEECUBELIST}    // glList  <-- Fastest
{.$DEFINE TEECUBEARRAYS}  // glDrawArrays
{.$DEFINE TEECUBEELEM}    // glDrawElements
{.$DEFINE TEECUBERANGE}   // glDrawRangeElements

{$IFNDEF TEECUBELIST}
{$IFNDEF TEECUBEARRAYS}
{$IFNDEF TEECUBEELEM}
{$IFNDEF TEECUBERANGE}
{$DEFINE TEECUBEINMEDIATE}
{$ENDIF}
{$ENDIF}
{$ENDIF}
{$ENDIF}

  TGLCanvas=class(TTeeNeutralCanvas)
  private
    { Private declarations }
    FAntialiasLevel: Integer;
    FColorDepth    : TGLColorDepth;
    FDepth         : Integer;
    FDrawStyle     : TTeeCanvasSurfaceStyle;
    FFontExtrusion : Single;
    FShininess     : Single;
    FTextureQuality : Integer;

    FWidth         : Integer;
    FHeight        : Integer;

    FOnFinish,
    FOnInit        : TNotifyEvent;

    { internal }
    FDC            : TTeeCanvasHandle;

    HRC            : HGLRC;
    FX             : Single;
    FY             : Single;
    FZ             : Single;
    FIs3D          : Boolean;

    // Fonts
    INumFonts      : Integer;

    FUseBuffer     : Boolean;
    IDrawToBitmap  : Boolean;
    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    IDestCanvas    : TCanvas;

    FQuadric       : PGLUQuadricObj;
    IQuadricTexture: Boolean;

    IMaxTexSize    : Integer;
    IOldColor      : TGLColorCache;
    IOldPenWidth   : Single;
    IOldPenStyle   : TPenStyle;
    IPenAlpha      : Boolean;
    ITextures      : Array of TTeeTexture;
    IOldDepth      : Integer;

    {$IFDEF TEECUBELIST}
    ICubeList      : Integer;
    ICubePenList   : Integer;
    {$ENDIF}

    ITess,
    ITess2D        : PGLUTesselator;

    // See Enable/Disable Rotation methods:
    IWasDepthTest,
    IWasLighting   : Byte;

    Function CalcZoom:Single;
    Procedure DeleteTexture(Index:Integer);
    Procedure DeleteTextures;
    Procedure DestroyGLContext;
    Function FontWeight:Integer;

    {$IFNDEF LINUX}
    Function GetDCHandle:HDC; {$IFDEF D10}inline;{$ENDIF}
    {$ENDIF}

    {$IFDEF TEECUBELIST}
    procedure InitLists;
    {$ENDIF}

    Procedure InitMatrix;

    Procedure InternalGradient( const P:TFourPoints;
                                const Colors:TTeeGradientColors;
                                Direction:TGradientDirection;
                                Balance:Integer=50;
                                RadialX:Integer=0;
                                RadialY:Integer=0;
                                const Z:Single=0); overload;

    procedure InternalGradient(const R:TRect; const Z:Single); overload;

    procedure InternalRectangle(const Rect: TRect); overload;
    procedure InternalRectangle(const Rect: TRect; const Z:Single); overload;

    function InternalUseBuffer:Boolean;

    procedure PrepareTess;
    procedure PrepareTessDirect;

    Function Quadric:PGLUQuadricObj;

    procedure SetAntialiasLevel(const Value:Integer);
    procedure SetColorDepth(const Value:TGLColorDepth);
    procedure SetFontExtrusion(const Value:Single);
    procedure SetPolygonMode;
    Procedure SetShininess(Const Value:Single);
  protected
    { Protected declarations }

    ITextureRepeatX : Single;
    ITextureRepeatY : Single;

    IAntiAlias      : Boolean;
    IActiveBlend    : Boolean;
    ITransp3D       : Boolean;

    IZeroAtCenter   : Boolean;

    ICanNPOTS       : Boolean;
    IRelaxedNPOTS   : Boolean;

    FOnGetPicBits   : TGLGetPicBits;

    procedure ApplyPolygonOffset(Enable:Boolean);

    {$IFNDEF LINUX}
    Procedure CreateFontOutlines(Index:Integer);
    {$ENDIF}

    procedure DoConcave({$IFDEF D6}const{$ENDIF} Points: Array of TAffineDblVector; const Invert:Boolean=False);

    function GetPixelAttribute(Attribute,APixelFormat:Integer; var Value:Integer):Boolean;

    function GetSync:TGLScreenSync;
    function GetTexQuality:Boolean;
    function GLTextureStyle:Cardinal;

    Procedure InitOpenGLFont;
    Procedure InitAmbientLight(AmbientLight:Integer);

    procedure InternalCylinder(Vertical:Boolean; Left,Top,Right,Bottom,
                        Z0,Z1:Integer; Dark3D:Boolean; ConePercent:Integer;
                        const Side1:TChartBrush=nil; const Side2:TChartBrush=nil);

    procedure InternalEllipse(const R:TRect; const Z:Single);

    Procedure EndBrushBitmap; overload;
    Procedure EndBrushBitmap(const Graphic:TGraphic); overload;

    function QuadricDrawStyle:Integer;

    procedure SetAntiAlias(const Value:Boolean); override;

    function SetBrushBitmap:Boolean; overload; {$IFDEF D10}inline;{$ENDIF}
    Function SetBrushBitmap(const Graphic:TGraphic):Boolean; overload;
    procedure SetBrushBitmap(const Graphic:TGraphic; AGLTexture:GLuint); overload;

    procedure SetDrawStyle(Value:TTeeCanvasSurfaceStyle);
    procedure SetSync(const Value:TGLScreenSync);
    procedure SetTexQuality(Value:Boolean);

    function BeginEntity(const Entity:String; const Visual:TVisualBlock=nil; const Transform:TTeeTransform=nil):TVisualBlock; override;
    procedure EndEntity; override;

    { 2d }
    Procedure BeginBrush(const Mode:GLEnum);

    procedure BeginPen; overload;
    Procedure BeginPen(const Mode:GLEnum); overload; {$IFDEF D9}inline;{$ENDIF}
    Procedure BeginPen(const Mode:GLEnum; const APen:TTeePen); overload;

    procedure DoChangedBrush(const ABrush:TTeeBrush); override;
    //procedure DoChangedFont; override;
    procedure DoChangedPen; override;

    procedure EndPen;

    function CanvasStyle:TCanvasStyle; override;
    Function GetHandle:TTeeCanvasHandle; override;
    Function GetPixel(x,y:Integer):TColor; override;
    function GetPixel3D(X,Y,Z:Integer): TColor; override;
    Function GetSupports3DText:Boolean; override;
    Function GetSupportsFullRotation:Boolean; override;
    Function GetSupportsXORMode:Boolean; override;
    Function GetUseBuffer:Boolean; override;

    function IsPenVisible:Boolean; {$IFDEF D10}inline;{$ENDIF}

    procedure SetPen(const APen:TTeePen);
    procedure SetPixel(X, Y: Integer; Value: TColor); override;
    procedure SetPixel3D(X,Y,Z:Integer; Value: TColor); override;
    Procedure SetUseBuffer(Value:Boolean); override;

    procedure SmoothShadow(const Shadow:TTeeShadow; const Rect:TRect;
                           Ellipse:Boolean; RoundSize:Integer;
                           const P:Array of TPoint;
                           DonutPercent:Integer=0;
                           const StartAngle:Double=0;
                           const EndAngle:Double=360); override;

    Procedure TeeNormal(const x,y,z:Integer); overload; {$IFDEF D9}inline;{$ENDIF}
    Procedure TeeNormal(const x,y,z:Single); overload; {$IFDEF D9}inline;{$ENDIF}

    Procedure TeeVertex2D(const x,y:Integer); {$IFDEF D9}inline;{$ENDIF}

    procedure TeeVertex3D(const P:TPoint; const z:Single); overload; {$IFDEF D9}inline;{$ENDIF}
    procedure TeeVertex3D(const P:TPoint; z:Integer); overload; {$IFDEF D9}inline;{$ENDIF}
    Procedure TeeVertex3D(const x,y,z:Integer); overload; {$IFDEF D9}inline;{$ENDIF}
    Procedure TeeVertex3D(const x,y,z:Single); overload; {$IFDEF D9}inline;{$ENDIF}
    procedure TeeVertex3D(const P:TPoint3D); overload; {$IFDEF D9}inline;{$ENDIF}

    procedure FastLineTo(const X, Y: Integer); override;
    procedure SetFastLine(const Active:Boolean); override;

    function Tessellate({$IFDEF D6}const{$ENDIF} X,Y: Array of TChartValue):TPointFloatArray; override;

  protected
    FOnProjection : TNotifyEvent;
    FontCache     : Array[0..TeeMaxFonts] of TGLFontCache;
    ISavedError   : GLEnum;
    UsesStencil   : Boolean;

    procedure ActivateBlend(const Value:Boolean);
    function AddNewTexture(AGraphic:TGraphic; AGLTexture:GLuint):TGraphic;
    procedure CalcMinMax(const Points: array of TPoint3DFloat;
                         var AMin,AMax : TPoint3DFloat);
    procedure CheckContext;
    Procedure DoProjection(DoPick:Boolean=False; x:Integer=0; y:Integer=0);
    Function FindFont:Integer;
    Function FindTexture(const AGraphic:TGraphic; out AGLTexture:GLuint):Boolean;

    Procedure InitLight(Num:Integer; const AAmbient,ADiffuse,ASpecular:GLMat;
                        const Position,Direction:TPoint3DFloat;
                        UseDirection:Boolean;
                        const SpotDegrees:Single;
                        SpotExponent:Integer);

    procedure RemoveTexture(const AGraphic: TGraphic);
    procedure SetModelView;
    procedure SetTextureParams;

    property CachedFonts:Integer read INumFonts;

  public
    AmbientLight   : Integer;
    FontStyle      : TTeeFontStyle;
    ShadeQuality   : Boolean;
    ZoomCorrection : Single;

    { Public declarations }
    Constructor Create; override;
    Destructor Destroy; override;

    procedure BufferToBitmap(const ABitmap:TBitmap; const R:TRect);
    function CheckGLError:Boolean; {$IFDEF D9}{$IFOPT C+}{$ELSE}inline;{$ENDIF}{$ENDIF}
    class function Description:String; override;
    class function EditorClass:String; override;

    Procedure DeleteFont(Index:Integer); overload;
    Procedure DeleteFont; overload;
    Procedure Repaint;

    { 3d }
    Procedure DisableRotation; override;
    Procedure EnableRotation; override;
    Procedure SetMaterialColor;

    Function BeginBlending(const R:TRect; Transparency:TTeeTransparency):TTeeBlend; override;
    procedure EndBlending(const Blend:TTeeBlend); override;

    procedure Arc(const Left, Top, Right, Bottom, StartX, StartY, EndX, EndY: Integer); override;
    procedure Donut( XCenter,YCenter,XRadius,YRadius:Integer;
                     Const StartAngle,EndAngle,HolePercent:Double); override;
    procedure Draw(X, Y: Integer; const Graphic: TGraphic); override;
    procedure EraseBackground(const Rect: TRect); override;
    procedure Ellipse(const X1, Y1, X2, Y2: Integer); override;
    procedure FillRect(const Rect: TRect); override;

    procedure LineTo(const X,Y:Integer); override;

    procedure MoveTo(const X,Y:Integer); override;
    procedure MoveTo(const P:TPointFloat); override;

    procedure Pie(const R:TRect; const StartAngle,EndAngle:Double); override;
    procedure Pie(X1, Y1, X2, Y2, X3, Y3, X4, Y4: Integer); override;

    procedure Rectangle(const X0,Y0,X1,Y1:Integer); override;
    procedure RoundRect(X1, Y1, X2, Y2, X3, Y3: Integer); override;
    Procedure SetColor(const AColor:TColor);
    procedure StretchDraw(const Rect: TRect; const Graphic: TGraphic); override;
    Procedure TextOut(X,Y:Integer; const Text:String); override;

    procedure ClipRectangle(Const Rect:TRect); override;

    Procedure GradientFill( Const Rect:TRect;
                            StartColor,EndColor:TColor;
                            Direction:TGradientDirection;
                            Balance:Integer=50;
                            RadialX:Integer=0;
                            RadialY:Integer=0); override;
    procedure Polyline(const Points:Array of TPoint; Z:Integer); override;
    procedure Polyline({$IFDEF D6}const{$ENDIF} Points:Array of TPoint); override;
    procedure Polyline(const Points:Array of TPoint3D); overload;
    procedure PolylineFloat(const Points:Array of TPointFloat); override;
    procedure PolylineFloat(const Points:Array of TPoint3DFloat); override;
    Procedure Polygon({$IFDEF D6}const{$ENDIF} Points: Array of TPoint); override;
    procedure PolygonConcave({$IFDEF D6}const{$ENDIF} Points: Array of TPoint); override;
    procedure PolygonConcave({$IFDEF D6}const{$ENDIF} Points: Array of TPoint; Z:Integer); override;
    procedure RotateLabel(x,y:Integer; Const St:String; RotDegree:Double; AllowHTML:Boolean=False); override;
    procedure UnClipRectangle; override;

    { 3d }
    function Calculate2DPosition(const x,y,z:Single):TPointFloat; overload;
    Procedure Calculate2DPosition(var x,y:Integer; z:Integer); overload; override;
    Function Calculate3DPosition(x,y,z:Integer):TPoint; override;
    Function InitWindow( const DestCanvas:TCanvas;
                         const A3DOptions:TView3DOptions;
                         ABackColor:TColor;
                         Is3D:Boolean;
                         Const UserRect:TRect):TRect; override;
    Procedure Projection(MaxDepth:Integer; const Bounds,Rect:TRect); override;
    Procedure ShowImage(const DestCanvas,DefaultCanvas:TCanvas; Const UserRect:TRect); override;
    Function ReDrawBitmap:Boolean; override;
    function RenderToBitmap(var ABitmap:TBitmap; const R:TRect):Boolean; override;

    Procedure Arrow( Filled:Boolean;
                     Const FromPoint,ToPoint:TPoint;
                     ArrowWidth,ArrowHeight,Z0,Z1:Integer;
                     const ArrowPercent:Double); override;

    procedure Cone( Vertical:Boolean; Left,Top,Right,Bottom,Z0,Z1:Integer;
                    Dark3D:Boolean; ConePercent:Integer); override;
    Procedure Cube(Left,Right,Top,Bottom,Z0,Z1:Integer; DarkSides:Boolean; RoundSize:Integer=0); overload; override;
    Procedure Cube({$IFNDEF TEECUBEINMEDIATE}const{$ENDIF} Left,Right,Top,Bottom,Z0,Z1:Single; DarkSides:Boolean; RoundSize:Integer=0); overload;
    procedure Cube(const MinBounds,MaxBounds:TPoint3DFloat); overload;

    procedure Cylinder(Vertical:Boolean; Left,Top,Right,Bottom,Z0,Z1:Integer; DarkCover:Boolean); override;

    procedure DrawCheckBox(x,y:Integer; Checked:Boolean; const ABackColor:TColor; CheckBox:Boolean=True); override;
    
    procedure EllipseWithZ(const X1, Y1, X2, Y2, Z:Integer); override;
    procedure Line(const A,B:TPoint3DFloat);
    procedure LineTo(const X, Y: Single); overload;

    procedure LineTo3D(const X,Y,Z:Integer); override;
    procedure LineTo3D(const X,Y,Z:Single); override;

    Procedure LineWithZ(X0,Y0,X1,Y1,Z:Integer); override;

    procedure MoveTo3D(const X,Y,Z:Integer); override;
    procedure MoveTo3D(const X,Y,Z:Single); override;

    procedure Pie3D( XCenter,YCenter,XRadius,YRadius,Z0,Z1:Integer;
                     Const StartAngle,EndAngle:Double;
                     DarkSides,DrawSides:Boolean;
                     DonutPercent:Integer=0;
                     Gradient:TCustomTeeGradient=nil;
                     BevelPercent:Integer=0;
                     EdgeStyle:TEdgeStyle=edNone;
                     Transparency:TTeeTransparency=0;
                     BevelBright:Integer=0;
                     BevelBorder:Boolean=True;
                     HideSides:Boolean=False); overload; override;

    procedure Plane3D(Const A,B:TPoint; Z0,Z1:Integer); override;
    procedure Plane3DFloat(Const A,B:TPointFloat; const Z0,Z1:Single);
    procedure PlaneWithZ(P1,P2,P3,P4:TPoint; Z:Integer); override;
    procedure PlaneFour3D(Var Points:TFourPoints; Z0,Z1:Integer); override;

    procedure Polygon3D(const Points: array of TPoint3D); overload; override;
    procedure Polygon3DFloat(const Points: array of TPoint3DFloat); overload;

    procedure Polygon3DConcave({$IFDEF D6}const{$ENDIF} Points: array of TPoint3DFloat; const Invert:Boolean=False); override;
    procedure Polygon3DConcave({$IFDEF D6}const{$ENDIF} Points: array of TPoint3D; const Invert:Boolean=False); override;

    procedure PolygonWithZ(const Points: array of TPoint; Z:Integer); override;
    procedure Pyramid(Vertical:Boolean; Left,Top,Right,Bottom,z0,z1:Integer; DarkSides:Boolean); override;
    Procedure PyramidTrunc(Const R:TRectF; const StartZ,EndZ,TruncX,TruncZ:Double;
                           TopCover:Boolean=True; BottomCover:Boolean=True); override;
    Procedure RectangleWithZ(Const Rect:TRect; Z:Integer); override;
    Procedure RectangleY(const Left,Top,Right,Z0,Z1:Integer); override;
    Procedure RectangleZ(const Left,Top,Bottom,Z0,Z1:Integer); override;
    procedure RotateLabel3D(x,y,z:Integer;
                            const St:String; RotDegree:Double;
                            AllowHTML:Boolean=False); override;
    procedure Sphere(x,y,z:Integer; const Radius:Double); overload; override;
    procedure Sphere(const x,y,z,Radius:Single); overload;
    procedure Sphere(const Center,Radius:TPoint3DFloat); overload;
    procedure StretchDraw(const Rect:TRect; const Graphic: TGraphic;
                          const Pos:Double; Plane:TCanvas3DPlane=cpZ); override;
    procedure Surface3D( Style:TTeeCanvasSurfaceStyle;
                         SameBrush,CreateCached,AddNormals:Boolean;
                         NumXValues,NumZValues:Integer;
                         const CalcPoints:TTeeCanvasCalcPoints;
                         var Cached:TTeeCachedObject); override;

    procedure Tetrahedron(Left,Top,Right,Bottom,z0,z1:Single);

    Procedure TextOut3DFloat(X,Y,Z:Single; const Text:String);
    Procedure TextOut3D(x,y,z:Integer; const Text:String; AllowHTML:Boolean); override;

    procedure Triangle3D(Const Points:TTrianglePoints3D; Const Colors:TTriangleColors3D); override;

    procedure Triangles({$IFDEF D6}const{$ENDIF} P:Array of TPoint3D;
                        CreateCached:Boolean;
                        var Cached:TTeeCachedObject); override;

    procedure TriangleWithZ(Const P1,P2,P3:TPoint; Z:Integer); override;

    procedure TriSurface(NumTriangles:Integer; CreateCached,AddNormals:Boolean;
                         const Blend:TTeeBlend; Transparency:TTeeTransparency;
                         const CalcPoints:TTeeCanvasTriPoints;
                         var Cached:TTeeCachedObject); override;

    // properties
    property AntialiasLevel:Integer read FAntialiasLevel write SetAntialiasLevel default 4;
    property ColorDepth:TGLColorDepth read FColorDepth write SetColorDepth default cd24Bit;
    property DrawStyle:TTeeCanvasSurfaceStyle read FDrawStyle write SetDrawStyle default tcsSolid;
    property FontExtrusion:Single read FFontExtrusion write SetFontExtrusion;
    property Shininess:Single read FShininess write FShininess;
    property TextureQuality:Boolean read GetTexQuality write SetTexQuality default True;
    property ScreenSync:TGLScreenSync read GetSync write SetSync default ssSystem;

    // events
    property OnInit:TNotifyEvent read FOnInit write FOnInit;
    property OnFinish:TNotifyEvent read FOnFinish write FOnFinish;
  end;

  TAlphaBitmap=class(TBitmap);

Procedure ColorToGL(const AColor:TColor; out C:GLMat); overload;
Function ColorToGL(const Value:Single):GLMat; overload;
{$NODEFINE ColorToGL}

var
  TeeSpotExponent:Integer=0;
  //TeeLODBias:Single=0;

implementation

uses
  Math;

const
  TeeMinPerspective = 6; // %

Function MinInteger(a,b:Integer):Integer; {$IFDEF D9}inline;{$ENDIF}
begin
  if a>b then result:=b else result:=a;
end;

Procedure ColorToGL(const AColor:TColor; out C:GLMat);
const
  Inv255=1/255;
begin
  C[0]:=TRGBA(AColor).Red*Inv255;
  C[1]:=TRGBA(AColor).Green*Inv255;
  C[2]:=TRGBA(AColor).Blue*Inv255;
  C[3]:=1-(TRGBA(AColor).Alpha*Inv255);
end;

{ TGLCachedObject }

Destructor TGLCachedObject.Destroy;
var t : Integer;
begin
  for t:=0 to Length(Lists)-1 do
      glDeleteLists(Lists[t],1);

  Lists:=nil;

  inherited;
end;

procedure TGLCachedObject.AddList;
var l : Integer;
begin
  l:=Length(Lists);

  SetLength(Lists,l+1);
  Lists[l]:=glGenLists(1);

  glNewList(Lists[l],GL_COMPILE_AND_EXECUTE);
end;

{ TGLCanvas }

Constructor TGLCanvas.Create;
begin
  inherited Create;

  InitOpenGL;

  FAntialiasLevel:=4;
  FUseBuffer:=True;
  ShadeQuality:=True;

  ISavedError:=GL_NO_ERROR;

  FSupportsAlpha:=True;
  FSupportsConcave:=False;

  ITextureRepeatX:=1;
  ITextureRepeatY:=1;

  FTextureQuality:=GL_LINEAR;

  AmbientLight:=100;

  ZoomCorrection:=2; // Experimental.  =1 is better for true-3d with perspective
  IZeroAtCenter:=True;

  {$IFDEF TEECUBELIST}
  InitLists;
  {$ENDIF}
end;

{$IFDEF TEECUBELIST}
Procedure TGLCanvas.InitLists;
begin
  ICubeList:=-1;
  ICubePenList:=-1;
end;
{$ENDIF}

Procedure TGLCanvas.DestroyGLContext;
begin
  DeleteFont;
  DeleteTextures;

  {$IFDEF TEECUBELIST}
  InitLists;
  {$ENDIF}

  if Assigned(ITess) then
  begin
    gluDeleteTess(ITess);

    // GLU: Do not call here: Assert(CheckGLError...)

    ITess:=nil;
  end;

  IOldPenWidth:=0;
  IOldColor.Transp:=0;
  IOldPenStyle:=psSolid;

  if HRC<>0 then
  begin
    {$IFNDEF LINUX}
    DeactivateRenderingContext(GetDCHandle);
    DestroyRenderingContext(HRC);
    {$ENDIF}

    HRC:=0;
  end;
end;

Destructor TGLCanvas.Destroy;
begin
  if Assigned(FQuadric) then
  begin
    gluDeleteQuadric(FQuadric);
    // GLU: Do not call here: Assert(CheckGLError...)
  end;

  {$IFDEF TEECUBELIST}
  glDeleteLists(ICubeList,1);
  glDeleteLists(ICubePenList,1);

  Assert(CheckGLError,'glDeleteLists: '+IntToStr(ISavedError));
  {$ENDIF}

  DestroyGLContext;

  inherited;
end;

class function TGLCanvas.Description:String;
begin
  result:='OpenGL'; // TeeMsg_OpenGL;
end;

class function TGLCanvas.EditorClass:String;
begin
  result:='TFormTeeGLEditor';
end;

{$DEFINE TEEREADPIXELS}

procedure TGLCanvas.BufferToBitmap(const ABitmap:TBitmap; const R:TRect);
var {$IFDEF TEEREADPIXELS}
    tmp : Pointer;
    {$ELSE}
    x,y,p : Integer;
    tmpLines : TRGBArray;
    tmpA : packed Array of Byte;
    {$ENDIF}
begin
  TeeSetBitmapSize(ABitmap,R.Right-R.Left,R.Bottom-R.Top);

  {$IFDEF TEEREADPIXELS}
  ABitmap.PixelFormat:=pf32bit;

  tmp:=ABitmap.ScanLine[ABitmap.Height-1];

  glPixelStorei(GL_PACK_ALIGNMENT,1);
  glReadPixels(0,0,ABitmap.Width,ABitmap.Height,GL_BGRA,GL_UNSIGNED_BYTE,tmp);

  Assert(CheckGLError,'glReadPixels: '+IntToStr(ISavedError));

  {$ELSE}
  SetLength(tmpA,3*ABitmap.Width*ABitmap.Height);
  TeeCalcLines(tmpLines,ABitmap);
  try
    glPixelStorei(GL_PACK_ALIGNMENT,1);

    glReadPixels(0,0,ABitmap.Width,ABitmap.Height,GL_RGB,GL_UNSIGNED_BYTE,tmpA);

    Assert(CheckGLError,'glReadPixels: '+IntToStr(ISavedError));

    p:=0;

    for y:=ABitmap.Height-1 downto 0 do
        for x:=0 to ABitmap.Width-1 do
        with tmpLines[y,x] do
        begin
          Red:=tmpA[p];
          Inc(p);
          Green:=tmpA[p];
          Inc(p);
          Blue:=tmpA[p];
          Inc(p);
        end;

  finally
    tmpLines:=nil;
    tmpA:=nil;
  end;
  {$ENDIF}
end;

Function TGLCanvas.CheckGLError:Boolean;
{$IFOPT C+}
var t : Integer;
{$ENDIF}
begin
  {$IFOPT C+}
  ISavedError:=glGetError;
  result:=ISavedError=GL_NO_ERROR;

  // Clean-up succesive errors. Use a protection loop counter.
  if not result then
  begin
    t:=0;

    while (t<100) and (glGetError<>GL_NO_ERROR) do
      Inc(t);
  end;

  {$ELSE}
  result:=True;
  {$ENDIF}
end;

function TGLCanvas.Calculate2DPosition(const x,y,z:Single):TPointFloat;
var viewport   : TVector4i;
    mvmatrix,
    projmatrix : TMatrix4d;
    wx, wy, wz : GLDouble;
begin
  glGetIntegerv(GL_VIEWPORT, @viewport);
  glGetDoublev(GL_MODELVIEW_MATRIX, @mvmatrix);
  glGetDoublev(GL_PROJECTION_MATRIX, @projmatrix);
  gluProject(x, y, z, mvmatrix, projmatrix, viewport, @wx, @wy, @wz);

  Assert(CheckGLError,'Calculate2DPosition: '+IntToStr(ISavedError));

  // Convert from Double to Single:
  result.x:=wx;
  result.y:=FHeight-wy;
end;

Procedure TGLCanvas.Calculate2DPosition(var x,y:Integer; z:Integer);
var P : TPointFloat;
begin
  P:=Calculate2DPosition(1.0*x,y,z);
  x:=Round(P.X);
  y:=-Round(P.Y);
end;

// OpenGL is already 3D, so no need to calculate a new XY position using Z
Function TGLCanvas.Calculate3DPosition(x,y,z:Integer):TPoint;
{var viewport   : TVector4i;
    mvmatrix,
    projmatrix : TMatrix4d;
    wx, wy, wz : GLDouble;}
begin
  {
    glGetIntegerv(GL_VIEWPORT, @viewport);
    glGetDoublev(GL_MODELVIEW_MATRIX, @mvmatrix);
    glGetDoublev(GL_PROJECTION_MATRIX, @projmatrix);
    y:=viewport[3]-y-1;
    gluUnProject(x, y, z, mvmatrix, projmatrix, viewport, @wx, @wy, @wz);
    result.x:=Round(wx);
    result.y:=Round(wy);
  }
  result.x:=x;
  result.y:=y;
end;

Procedure TGLCanvas.DoProjection(DoPick:Boolean=False;
                                 x:Integer=0;
                                 y:Integer=0);
const
  ZNear = 2;

var tmp  : Single;
    FarZ : Integer;
    tmpAngle,
    tmpAspect,
    tmpW,
    tmpH : Single;
    tmpFoV : Single;
    viewport : TVector4i;
begin
  glMatrixMode(GL_PROJECTION);
  Assert(CheckGLError,'Projection: '+IntToStr(ISavedError));

  glLoadIdentity;
  Assert(CheckGLError,'ProjectionInit');

  if DoPick then
  begin
    glGetIntegerv(GL_VIEWPORT, @viewport);
    gluPickMatrix(x,FHeight-y-1,4,4,viewport);

    Assert(CheckGLError,'gluPickMatrix: '+IntToStr(ISavedError));
  end;

  FarZ:=Round(400.0*(FDepth+1));

  if (not FIs3D) or View3DOptions.Orthogonal then
  begin
    tmpW:=(FWidth)*0.5;
    tmpH:=(FHeight)*0.5;
    tmp:=200.0/CalcZoom;

    glOrtho(-tmpW*tmp,tmpW*tmp,-tmpH*tmp,tmpH*tmp,-0.1,tmp*2.0*FarZ);
    glDisable(GL_DEPTH_TEST);

    Assert(CheckGLError,'Orthogonal');
  end
  else
  begin
    glEnable(GL_DEPTH_TEST);

    tmp:=100.0/CalcZoom;
    if tmp<1 then tmp:=1;

    tmpFoV:=0.5*Math.Max(TeeMinPerspective,View3DOptions.Perspective);

//    tmpH:=Tan(tmpFoV * Pi / 360.0 )*2;
//    tmpW:=tmpH*(FWidth/FHeight);
//    glFrustum( -tmpW, tmpW, -tmpH, tmpH, ZNear, 5*tmp*FarZ );

    tmpAngle:=Tan(tmpFoV * 0.5 * Pi / 180.0 );
    tmpH:=3*ZNear*tmpAngle;
    tmpAspect:=1.0*FWidth/FHeight;
    glFrustum( -(tmpH*tmpAspect), (tmpH*tmpAspect), -tmpH, tmpH, ZNear, 5*tmp*FarZ );

    Assert(CheckGLError,'Perspective');
  end;
end;

Procedure TGLCanvas.Projection(MaxDepth:Integer; const Bounds,Rect:TRect);
begin
  RectSize(Bounds,FWidth,FHeight);

  RectCenter(Rect,FXCenter,FYCenter);

  FDepth:=MaxDepth;
  FZCenter:=Round(FDepth*0.5);

  glViewport(0, 0, FWidth+1, FHeight+1); // Inflate +1

  Assert(CheckGLError,'ViewPort '+IntToStr(ISavedError));

  DoProjection;
  SetDrawStyle(FDrawStyle);
  InitMatrix;
  SetModelView;
end;

Function TGLCanvas.CalcZoom:Single;

  Function CalcPerspective:Single;
  begin
    if FIs3D and (not View3DOptions.Orthogonal) then
       result:=Math.Max(TeeMinPerspective,View3DOptions.Perspective)*0.01
    else
       result:=1;
  end;

begin
  result:=2.0*Math.Max(0.1,View3DOptions.ZoomFloat)*CalcPerspective;
end;

Procedure TGLCanvas.InitMatrix;
const tmpInv=1/255.0;
var AColor : TColor;
begin
  AColor:=BackColor;

  glClearColor( Byte(AColor)*tmpInv,
                Byte(AColor shr 8)*tmpInv,
                Byte(AColor shr 16)*tmpInv,
                1);
  Assert(CheckGLError,'ClearColor');

  glDisable(GL_DITHER);

  if UsesStencil then
     glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT or GL_STENCIL_BUFFER_BIT)
  else
     glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

  Assert(CheckGLError,'Clear');

  glEnable(GL_DITHER);

  if ShadeQuality then glShadeModel(GL_SMOOTH)
                  else glShadeModel(GL_FLAT);

  Assert(CheckGLError,'ShadeModel');

  if ShadeQuality then gluQuadricNormals(Quadric,GLU_SMOOTH)
                  else gluQuadricNormals(Quadric,GLU_FLAT);

  if IAntiAlias and (not IDrawToBitmap) then
  begin
    if ITransp3D then
       glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB)
    else
       glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB);

    Assert(CheckGLError,'GL_SAMPLE_ALPHA_TO_COVERAGE_ARB');
  end;

  InitAmbientLight(AmbientLight);

  if Assigned(FOnInit) then
     FOnInit(Self);

  Assert(CheckGLError,'Init');
end;

procedure TGLCanvas.SetModelView;

  procedure DoTilt;
  begin
    if View3DOptions.TiltFloat<>0 then
    with RotationCenter do
    begin
      glTranslatef(X,Y,0);
      glRotatef(View3DOptions.TiltFloat, 0, 0, 1);
      glTranslatef(-X,-Y,0);
    end;
  end;

var tmpZ : Single;
begin
  glMatrixMode(GL_MODELVIEW);
  Assert(CheckGLError,'ModelView');

  glLoadIdentity;
  Assert(CheckGLError,'ModelInit');

  if Assigned(FOnProjection) then
     FOnProjection(Self)
  else
  begin
    with View3DOptions do
    begin
      if FIs3D and Orthogonal then
         tmpZ:=-1000 // Fix 851, clipping 3D Z in Orthogonal mode.
      else
         tmpZ:=ZOffset+(-60000/(ZoomCorrection*CalcZoom));

      glTranslatef(HorizOffsetFloat,-VertOffsetFloat,tmpZ);

      if FIs3D then
      begin
        with RotationCenter do
        begin
          DoTilt;

          glTranslatef(0,Y,Z);
          glRotatef(-ElevationFloat, 1, 0, 0);
          glTranslatef(X,-Y,0);
          glRotatef(RotationFloat, 0, 1, 0);
          glTranslatef(-X,0,-Z);
        end;
      end
      else
        DoTilt;

      if IZeroAtCenter then
         glTranslatef(-XCenter,YCenter,ZCenter);

      Assert(CheckGLError,'Rotations');
    end;
  end;

  Assert(CheckGLError,'Model');
end;

procedure TGLCanvas.ActivateBlend(const Value:Boolean);
begin
  if IActiveBlend<>Value then
  begin
    if Value then
    begin
      glEnable(GL_BLEND);
      glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

      Assert(CheckGLError,'glBlendFunc: '+IntToStr(ISavedError));

      IActiveBlend:=True;
    end
    else
    begin
      glDisable(GL_BLEND);

      Assert(CheckGLError,'glDisable GL_BLEND: '+IntToStr(ISavedError));

      IActiveBlend:=False;
    end;
  end;
end;

function TGLCanvas.GetPixelAttribute(Attribute,APixelFormat:Integer; var Value:Integer):Boolean;
var attrib:Array[0..1] of Integer;
    results:Array[0..0] of Integer;
begin
  if Assigned(wglGetPixelFormatAttribivARB) then
  begin
    attrib[0]:=Attribute;
    attrib[1]:=0;

    result:=wglGetPixelFormatAttribivARB(GetDCHandle,
                         APixelFormat,
                         PFD_MAIN_PLANE, 1, @attrib, @results);

    if result then
       Value:=results[0];
  end
  else
    result:=False;
end;

procedure TGLCanvas.SetAntiAlias(const Value:Boolean);

  function IsMultiSampleContext:Boolean;
  var tmp : Integer;
  begin
    result:=GetPixelAttribute(WGL_SAMPLE_BUFFERS_ARB,GetPixelFormat(GetDCHandle),tmp) and (tmp=GL_TRUE);
  end;

begin
  if IAntiAlias<>Value then
  begin
    IAntiAlias:=Value;

    // Enable / Disable antialias smoothing:
    if Value and (not IDrawToBitmap) then
    begin
      glEnable(GL_ARB_MULTISAMPLE);

      if not IsMultiSampleContext then
      begin
        //glEnable(GL_POLYGON_SMOOTH); // <--Warning: Fonts and polygon 3D Depth test fails

        glEnable(GL_POINT_SMOOTH);
        glEnable(GL_LINE_SMOOTH);
      end;
    end
    else
    begin
      glDisable(GL_POLYGON_SMOOTH);
      glDisable(GL_POINT_SMOOTH);
      glDisable(GL_LINE_SMOOTH);

      if not IDrawToBitmap then
         glDisable(GL_ARB_MULTISAMPLE);
    end;

    Assert(CheckGLError,'SetAntiAlias: '+IntToStr(ISavedError));
  end;
end;

Function TGLCanvas.Quadric:PGLUQuadricObj;
begin
  if not Assigned(FQuadric) then
     FQuadric:=gluNewQuadric;

  result:=FQuadric;
end;

procedure TGLCanvas.SetAntialiasLevel(const Value:Integer);
begin
  if FAntialiasLevel<>Value then
  begin
    FAntialiasLevel:=Value;
    IDestCanvas:=nil; // <-- flag to recreate GL context
    Repaint;
  end;
end;

procedure TGLCanvas.SetColorDepth(const Value:TGLColorDepth);
begin
  if FColorDepth<>Value then
  begin
    FColorDepth:=Value;
    IDestCanvas:=nil; // <-- flag to recreate GL context
    Repaint;
  end;
end;

procedure TGLCanvas.SetFontExtrusion(const Value:Single);
begin
  if FFontExtrusion<>Value then
  begin
    FFontExtrusion:=Value;
    Repaint;
  end;
end;

Procedure TGLCanvas.TeeVertex2D(const x,y:Integer);
begin
  glVertex2i(x,-y);
end;

procedure TGLCanvas.TeeVertex3D(const P:TPoint3D);
begin
  glVertex3f(P.x,-P.y,-P.z);
end;

procedure TGLCanvas.TeeVertex3D(const P:TPoint; const z:Single);
begin
  glVertex3f(P.x,-P.y,-z);
end;

procedure TGLCanvas.TeeVertex3D(const P:TPoint; z:Integer);
begin
  glVertex3i(P.x,-P.y,-z);
end;

Procedure TGLCanvas.TeeVertex3D(const x,y,z:Single);
begin
  glVertex3f(x,-y,-z);
end;

Procedure TGLCanvas.TeeVertex3D(const x,y,z:Integer);
begin
  glVertex3i(x,-y,-z);
end;

Procedure TGLCanvas.TeeNormal(const x,y,z:Single);
begin
  glNormal3f(x,y,-z);
end;

Procedure TGLCanvas.TeeNormal(const x,y,z:Integer);
begin
  glNormal3i(x,y,-z);
end;

function TGLCanvas.SetBrushBitmap:Boolean;
begin
  result:=SetBrushBitmap(Brush.Bitmap);
end;

function TGLCanvas.GLTextureStyle:Cardinal;
begin
  if ICanNPOTS then
     result:=GL_TEXTURE_RECTANGLE_ARB
  else
     result:=GL_TEXTURE_2D;
end;

procedure TGLCanvas.SetBrushBitmap(const Graphic:TGraphic; AGLTexture:GLuint);
begin
  if ICanNPOTS then
  begin
    ITextureRepeatX:=Graphic.Width;
    ITextureRepeatY:=Graphic.Height;
  end
  else
  begin
    ITextureRepeatX:=1;
    ITextureRepeatY:=1;
  end;

  glEnable(GLTextureStyle);
  Assert(CheckGLError,'Enable Texture 2D: '+IntToStr(ISavedError));

  {$IFNDEF LINUX}
  glBindTexture(GLTextureStyle, AGLTexture);
  Assert(CheckGLError,'BindTexture: '+IntToStr(ISavedError));
  {$ENDIF}

  gluQuadricTexture(Quadric,{$IFDEF LINUX}True{$ELSE}GL_TRUE{$ENDIF});
  Assert(CheckGLError,'gluQuadricTexture');
  IQuadricTexture:=True;

  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, TeeTextureEnvMode);
  Assert(CheckGLError,'glTexEnvi: '+IntToStr(ISavedError));

  if Graphic.Transparent then
     ActivateBlend(True);
end;

Function TGLCanvas.SetBrushBitmap(const Graphic:TGraphic):Boolean;
var tmp : GLUInt;
begin
  if Assigned(Graphic) and (Graphic.Width<>0) then
  begin
    result:=FindTexture(Graphic,tmp);

    if result then
       SetBrushBitmap(Graphic,tmp);

    Assert(CheckGLError,'SetBrushBitmap '+IntToStr(ISavedError));
  end
  else
    result:=True;
end;

Procedure TGLCanvas.EndBrushBitmap;
begin
  EndBrushBitmap(Brush.Bitmap);
end;

Procedure TGLCanvas.EndBrushBitmap(const Graphic:TGraphic);
begin
  if Assigned(Graphic) then
  begin
    if IQuadricTexture then
    begin
      gluQuadricTexture(Quadric,{$IFDEF LINUX}False{$ELSE}GL_FALSE{$ENDIF});
      Assert(CheckGLError,'gluQuadricTexture: '+IntToStr(ISavedError));
      IQuadricTexture:=False;
    end;

    glDisable(GLTextureStyle);
  end;
end;

Procedure TGLCanvas.Cube(Left,Right,Top,Bottom,Z0,Z1:Integer; DarkSides:Boolean; RoundSize:Integer);
var tmp : Single;
begin
  tmp:=Left;
  Cube(tmp,Right,Top,Bottom,Z0,Z1,DarkSides,RoundSize);
end;

function TGLCanvas.BeginEntity(const Entity:String; const Visual:TVisualBlock=nil; const Transform:TTeeTransform=nil):TVisualBlock;
var tmpScale,
    tmpRotate,
    tmpUseOrigin : Boolean;
begin
  glPushMatrix;

  if Assigned(Transform) and Transform.Enabled then
  begin
    // Pending: Clip

    with Transform.Translate do
         glTranslatef(X,Y,Z);

    tmpScale:=(Transform.Scale.X<>1) or (Transform.Scale.Y<>1) or (Transform.Scale.Z<>1);
    tmpRotate:=(Transform.Rotate<>0);
    tmpUseOrigin:=tmpRotate or tmpScale;

    if tmpUseOrigin then
       glTranslatef(Transform.Origin.X,-Transform.Origin.Y,0);

    if tmpRotate then
       if FIs3D then
          glRotatef(360-Transform.Rotate,1,0,0)
       else
          glRotatef(360-Transform.Rotate,0,0,1);

    if tmpScale then
       with Transform.Scale do
            glScalef(X,Y,Z);

    if tmpUseOrigin then
       glTranslatef(-Transform.Origin.X,Transform.Origin.Y,0);
  end;

  Assert(CheckGLError,'BeginEntity: '+IntToStr(ISavedError));

  result:=Visual;
end;

procedure TGLCanvas.EndEntity;
begin
  glPopMatrix;
end;

procedure TGLCanvas.BeginBrush(const Mode:GLEnum);
begin
  SetBrushBitmap;
  SetColor(Brush.Color);

  // Important: glBegin must be done *after* SetColor (GL_BLEND)
  glBegin(Mode);
end;

procedure TGLCanvas.Cube(const MinBounds,MaxBounds:TPoint3DFloat);
begin
  Cube(MinBounds.X,MaxBounds.X,MinBounds.Y,MaxBounds.Y,MinBounds.Z,MaxBounds.Z,False);
end;

procedure TGLCanvas.DrawCheckBox(x,y:Integer; Checked:Boolean; const ABackColor:TColor; CheckBox:Boolean=True);
begin
  InternalDrawCheck(x,y,Checked,ABackColor,CheckBox);
end;

{$IFDEF TEECUBEARRAYS}
// Code from http://www.songho.ca/opengl/gl_vertexarray.html
const CubeNormals:Array[0..(36*3)-1] of Single=
                      ( 0, 0, 1,   0, 0, 1,   0, 0, 1,      // v0-v1-v2 (front)
                        0, 0, 1,   0, 0, 1,   0, 0, 1,      // v2-v3-v0

                        1, 0, 0,   1, 0, 0,   1, 0, 0,      // v0-v3-v4 (right)
                        1, 0, 0,   1, 0, 0,   1, 0, 0,      // v4-v5-v0

                        0, 1, 0,   0, 1, 0,   0, 1, 0,      // v0-v5-v6 (top)
                        0, 1, 0,   0, 1, 0,   0, 1, 0,      // v6-v1-v0

                       -1, 0, 0,  -1, 0, 0,  -1, 0, 0,      // v1-v6-v7 (left)
                       -1, 0, 0,  -1, 0, 0,  -1, 0, 0,      // v7-v2-v1

                        0,-1, 0,   0,-1, 0,   0,-1, 0,      // v7-v4-v3 (bottom)
                        0,-1, 0,   0,-1, 0,   0,-1, 0,      // v3-v2-v7

                        0, 0,-1,   0, 0,-1,   0, 0,-1,      // v4-v7-v6 (back)
                        0, 0,-1,   0, 0,-1,   0, 0,-1 );    // v6-v5-v4

const CubeVertices:Array[0..(36*3)-1] of Single=
                      ( 1, 1, 1,  -1, 1, 1,  -1,-1, 1,      // v0-v1-v2 (front)
                       -1,-1, 1,   1,-1, 1,   1, 1, 1,      // v2-v3-v0

                        1, 1, 1,   1,-1, 1,   1,-1,-1,      // v0-v3-v4 (right)
                        1,-1,-1,   1, 1,-1,   1, 1, 1,      // v4-v5-v0

                        1, 1, 1,   1, 1,-1,  -1, 1,-1,      // v0-v5-v6 (top)
                       -1, 1,-1,  -1, 1, 1,   1, 1, 1,      // v6-v1-v0

                       -1, 1, 1,  -1, 1,-1,  -1,-1,-1,      // v1-v6-v7 (left)
                       -1,-1,-1,  -1,-1, 1,  -1, 1, 1,      // v7-v2-v1

                       -1,-1,-1,   1,-1,-1,   1,-1, 1,      // v7-v4-v3 (bottom)
                        1,-1, 1,  -1,-1, 1,  -1,-1,-1,      // v3-v2-v7

                        1,-1,-1,  -1,-1,-1,  -1, 1,-1,      // v4-v7-v6 (back)
                       -1, 1,-1,   1, 1,-1,   1,-1,-1 );    // v6-v5-v4
{$ENDIF}

{$IFDEF TEECUBEELEM}
const CubeVerticesElem:Array[0..71] of Single=
                      ( 1, 1, 1,  -1, 1, 1,  -1,-1, 1,   1,-1, 1,   // v0,v1,v2,v3 (front)
                        1, 1, 1,   1,-1, 1,   1,-1,-1,   1, 1,-1,   // v0,v3,v4,v5 (right)
                        1, 1, 1,   1, 1,-1,  -1, 1,-1,  -1, 1, 1,   // v0,v5,v6,v1 (top)
                       -1, 1, 1,  -1, 1,-1,  -1,-1,-1,  -1,-1, 1,   // v1,v6,v7,v2 (left)
                       -1,-1,-1,   1,-1,-1,   1,-1, 1,  -1,-1, 1,   // v7,v4,v3,v2 (bottom)
                        1,-1,-1,  -1,-1,-1,  -1, 1,-1,   1, 1,-1 ); // v4,v7,v6,v5 (back)

const CubeNormalsElem:Array[0..71] of Single=
                      ( 0, 0, 1,   0, 0, 1,   0, 0, 1,   0, 0, 1,   // v0,v1,v2,v3 (front)
                        1, 0, 0,   1, 0, 0,   1, 0, 0,   1, 0, 0,   // v0,v3,v4,v5 (right)
                        0, 1, 0,   0, 1, 0,   0, 1, 0,   0, 1, 0,   // v0,v5,v6,v1 (top)
                       -1, 0, 0,  -1, 0, 0,  -1, 0, 0,  -1, 0, 0,   // v1,v6,v7,v2 (left)
                        0,-1, 0,   0,-1, 0,   0,-1, 0,   0,-1, 0,   // v7,v4,v3,v2 (bottom)
                        0, 0,-1,   0, 0,-1,   0, 0,-1,   0, 0,-1 ); // v4,v7,v6,v5 (back)


const CubeIndices:Array[0..35] of Byte=
                     ( 0, 1, 2,   2, 3, 0,      // front
                       4, 5, 6,   6, 7, 4,      // right
                       8, 9,10,  10,11, 8,      // top
                      12,13,14,  14,15,12,      // left
                      16,17,18,  18,19,16,      // bottom
                      20,21,22,  22,23,20 );    // back
{$ENDIF}

Procedure TGLCanvas.Cube({$IFNDEF TEECUBEINMEDIATE}const{$ENDIF} Left,Right,Top,Bottom,Z0,Z1:Single; DarkSides:Boolean; RoundSize:Integer);

  {$IFNDEF TEECUBEINMEDIATE}
  procedure InitMatrixCube;
  begin
    glPushMatrix;
    glTranslatef((Left+Right)*0.5,(Top+Bottom)*0.5,(z0+z1)*0.5);
    glScalef(Abs(Right-Left),Abs(Bottom-Top),Abs(z1-z0));
  end;
  {$ENDIF}

begin
  {$IFDEF TEECUBEINMEDIATE}
  if Left>Right then SwapSingle(Left,Right);
  if Top>Bottom then SwapSingle(Top,Bottom);
  if z0>z1 then SwapSingle(z0,z1);

  Top:=-Top;
  Bottom:=-Bottom;
  z0:=-z0;
  z1:=-z1;
  {$ENDIF}

  if Brush.Style<>bsClear then
  begin
    glEnable(GL_CULL_FACE);

    if Assigned(Brush.Bitmap) and (Brush.Bitmap.Width<>0) then
    begin
      BeginBrush(GL_QUADS);

      glNormal3i( 0, 0, 1);
       glTexCoord2f(ITextureRepeatX,0);
       glVertex3f( Left,  Bottom, z0);
       glTexCoord2f(ITextureRepeatX,ITextureRepeatY);
       glVertex3f( Right, Bottom, z0);
       glTexCoord2f(0,ITextureRepeatY);
       glVertex3f( Right, Top,    z0);
       glTexCoord2s(0,0);
       glVertex3f( Left,  Top,    z0);

      glNormal3i(-1,  0,  0);
       glTexCoord2s(0,0);
       glVertex3f( Left, Top,    z1);
       glTexCoord2f(0,ITextureRepeatY);
       glVertex3f( Left, Bottom, z1);
       glTexCoord2f(ITextureRepeatX,ITextureRepeatY);
       glVertex3f( Left, Bottom, z0);
       glTexCoord2f(ITextureRepeatX,0);
       glVertex3f( Left, Top,    z0);

      glNormal3i( 0, 0, -1);
       glTexCoord2s(0,0);
       glVertex3f( Right, Top,    z1);
       glTexCoord2f(0,ITextureRepeatY);
       glVertex3f( Right, Bottom, z1);
       glTexCoord2f(ITextureRepeatX,ITextureRepeatY);
       glVertex3f( Left,  Bottom, z1);
       glTexCoord2f(ITextureRepeatX,0);
       glVertex3f( Left,  Top,    z1);

      glNormal3i( 1,  0,  0);
       glTexCoord2s(0,0);
       glVertex3f( Right, Bottom, z0);
       glTexCoord2f(0,ITextureRepeatY);
       glVertex3f( Right, Bottom, z1);
       glTexCoord2f(ITextureRepeatX,ITextureRepeatY);
       glVertex3f( Right, Top,    z1);
       glTexCoord2f(ITextureRepeatX,0);
       glVertex3f( Right, Top,    z0);

      glNormal3i( 0, 1,  0);
       glTexCoord2s(0,0);
       glVertex3f( Left,  Top, z1);
       glTexCoord2f(0,ITextureRepeatY);
       glVertex3f( Left,  Top, z0);
       glTexCoord2f(ITextureRepeatX,ITextureRepeatY);
       glVertex3f( Right, Top, z0);
       glTexCoord2f(ITextureRepeatX,0);
       glVertex3f( Right, Top, z1);

      glNormal3i( 0, -1,  0);
       glTexCoord2s(0,0);
       glVertex3f( Right, Bottom, z0);
       glTexCoord2f(0,ITextureRepeatY);
       glVertex3f( Left,  Bottom, z0);
       glTexCoord2f(ITextureRepeatX,ITextureRepeatY);
       glVertex3f( Left,  Bottom, z1);
       glTexCoord2f(ITextureRepeatX,0);
       glVertex3f( Right, Bottom, z1);

      glEnd;

      EndBrushBitmap;
    end
    else
    begin

      {$IFDEF TEECUBELIST}
      if ICubeList=-1 then
      begin
        ICubeList:=glGenLists(1);
        glNewList(ICubeList,GL_COMPILE);

        glBegin(GL_QUADS);

        glNormal3i( 0, 0, 1);
         glVertex3f( 0, 1, 0);
         glVertex3f( 1, 1, 0);
         glVertex3f( 1, 0, 0);
         glVertex3f( 0, 0, 0);

        glNormal3i(-1,  0, 0);
         glVertex3f( 0, 0, 1);
         glVertex3f( 0, 1, 1);
         glVertex3f( 0, 1, 0);
         glVertex3f( 0, 0, 0);

        glNormal3i( 0, 0, -1);
         glVertex3f( 1, 0, 1);
         glVertex3f( 1, 1, 1);
         glVertex3f( 0, 1, 1);
         glVertex3f( 0, 0, 1);

        glNormal3i( 1,  0,  0);
         glVertex3f( 1, 1, 0);
         glVertex3f( 1, 1, 1);
         glVertex3f( 1, 0, 1);
         glVertex3f( 1, 0, 0);

        glNormal3i( 0, 1,  0);
         glVertex3f( 0, 0, 1);
         glVertex3f( 0, 0, 0);
         glVertex3f( 1, 0, 0);
         glVertex3f( 1, 0, 1);

        glNormal3i( 0, -1,  0);
         glVertex3f( 1, 1, 0);
         glVertex3f( 0, 1, 0);
         glVertex3f( 0, 1, 1);
         glVertex3f( 1, 1, 1);

        glEnd;
        Assert(CheckGLError,'Cube');

        glEndList;

        Assert(CheckGLError,'Cube');
      end;

      InitMatrixCube;

      SetColor(Brush.Color);
      Assert(CheckGLError,'Cube');

      glCallList(ICubeList);

      glPopMatrix;
      Assert(CheckGLError,'Cube: '+IntToStr(ISavedError));
      {$ELSE}

      {$IFDEF TEECUBEARRAYS}
      glEnableClientState(GL_NORMAL_ARRAY);
      glEnableClientState(GL_VERTEX_ARRAY);
      glNormalPointer(GL_FLOAT, 0, @CubeNormals);
      glVertexPointer(3, GL_FLOAT, 0, @CubeVertices);

      InitMatrixCube;

      SetColor(Brush.Color);

      glDrawArrays(GL_TRIANGLES, 0, 36);

      glPopMatrix;

      glDisableClientState(GL_VERTEX_ARRAY);
      glDisableClientState(GL_NORMAL_ARRAY);

      {$ELSE}

      {$IFDEF TEECUBEELEM}
      glEnableClientState(GL_NORMAL_ARRAY);
      glEnableClientState(GL_VERTEX_ARRAY);
      glNormalPointer(GL_FLOAT, 0, @CubeNormalsElem);
      glVertexPointer(3, GL_FLOAT, 0, @CubeVerticesElem);

      InitMatrixCube;

      SetColor(Brush.Color);
      glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_BYTE, @CubeIndices);

      glPopMatrix;

      glDisableClientState(GL_VERTEX_ARRAY);
      glDisableClientState(GL_NORMAL_ARRAY);

      {$ELSE}

      SetColor(Brush.Color);
      glBegin(GL_QUADS);

      glNormal3i( 0, 0, 1);
       glVertex3f( Left,  Bottom, z0);
       glVertex3f( Right, Bottom, z0);
       glVertex3f( Right, Top,    z0);
       glVertex3f( Left,  Top,    z0);

      glNormal3i(-1,  0,  0);
       glVertex3f( Left, Top,    z1);
       glVertex3f( Left, Bottom, z1);
       glVertex3f( Left, Bottom, z0);
       glVertex3f( Left, Top,    z0);

      glNormal3i( 0, 0, -1);
       glVertex3f( Right, Top,    z1);
       glVertex3f( Right, Bottom, z1);
       glVertex3f( Left,  Bottom, z1);
       glVertex3f( Left,  Top,    z1);

      glNormal3i( 1,  0,  0);
       glVertex3f( Right, Bottom, z0);
       glVertex3f( Right, Bottom, z1);
       glVertex3f( Right, Top,    z1);
       glVertex3f( Right, Top,    z0);

      glNormal3i( 0, 1,  0);
       glVertex3f( Left,  Top, z1);
       glVertex3f( Left,  Top, z0);
       glVertex3f( Right, Top, z0);
       glVertex3f( Right, Top, z1);

      glNormal3i( 0, -1,  0);
       glVertex3f( Right, Bottom, z0);
       glVertex3f( Left,  Bottom, z0);
       glVertex3f( Left,  Bottom, z1);
       glVertex3f( Right, Bottom, z1);

      glEnd;

      {$ENDIF}
      {$ENDIF}
      {$ENDIF}
    end;

    glDisable(GL_CULL_FACE);
  end;

  if IsPenVisible then
  begin
    {$IFDEF TEECUBELIST}
    if ICubePenList=-1 then
    begin
      ICubePenList:=glGenLists(1);
      glNewList(ICubePenList,GL_COMPILE);

      glBegin(GL_LINE_LOOP);
        glVertex3f( 0, 1, 0);
        glVertex3f( 1, 1, 0);
        glVertex3f( 1, 0, 0);
        glVertex3f( 0, 0, 0);
      glEnd;

      glBegin(GL_LINE_LOOP);
        glVertex3f( 0, 0, 1);
        glVertex3f( 0, 1, 1);
        glVertex3f( 1, 1, 1);
        glVertex3f( 1, 0, 1);
      glEnd;

      glBegin(GL_LINE_LOOP);
        glVertex3f( 1, 0, 0);
        glVertex3f( 1, 0, 1);
        glVertex3f( 1, 1, 1);
        glVertex3f( 1, 1, 0);
      glEnd;

      glBegin(GL_LINE_LOOP);
        glVertex3f( 0, 0, 0);
        glVertex3f( 0, 1, 0);
        glVertex3f( 0, 1, 1);
        glVertex3f( 0, 0, 1);
      glEnd;

      glBegin(GL_LINE_LOOP);
        glVertex3f( 1, 0, 1);
        glVertex3f( 1, 0, 0);
        glVertex3f( 0, 0, 0);
        glVertex3f( 0, 0, 1);
      glEnd;

      glEndList;
    end;

    BeginPen;

    InitMatrixCube;

    Assert(CheckGLError,'Cube Pen');

    glCallList(ICubePenList);

    glPopMatrix;

    {$ELSE}
    BeginPen(GL_LINE_LOOP);
      glVertex3f( Left,  Bottom, z0);
      glVertex3f( Right, Bottom, z0);
      glVertex3f( Right, Top,    z0);
      glVertex3f( Left,  Top,    z0);
    glEnd;

    glBegin(GL_LINE_LOOP);
      glVertex3f( Left,  Top,    z1);
      glVertex3f( Left,  Bottom, z1);
      glVertex3f( Right, Bottom, z1);
      glVertex3f( Right, Top,    z1);
    glEnd;

    glBegin(GL_LINE_LOOP);
      glVertex3f( Right, Top,    z0);
      glVertex3f( Right, Top,    z1);
      glVertex3f( Right, Bottom, z1);
      glVertex3f( Right, Bottom, z0);
    glEnd;

    glBegin(GL_LINE_LOOP);
      glVertex3f( Left, Top,    z0);
      glVertex3f( Left, Bottom, z0);
      glVertex3f( Left, Bottom, z1);
      glVertex3f( Left, Top,    z1);
    glEnd;

    glBegin(GL_LINE_LOOP);
      glVertex3f( Right, Top, z1);
      glVertex3f( Right, Top, z0);
      glVertex3f( Left,  Top, z0);
      glVertex3f( Left,  Top, z1);
    glEnd;
    {$ENDIF}

    EndPen;
  end;

  Assert(CheckGLError,'Cube');
end;

Function ColorToGL(Const Value:Single):GLMat;
begin
  result[0]:=Value;
  result[1]:=Value;
  result[2]:=Value;
  result[3]:=1;
end;

Procedure TGLCanvas.SetMaterialColor;
var tmp : GLMat;
begin
  tmp:=ColorToGL(TeeMaterialDiffuse);
  glMaterialfv(TeeColorPlanes,GL_DIFFUSE,PGLFloat(@tmp));

  tmp:=ColorToGL(TeeMaterialSpecular);
  glMaterialfv(TeeColorPlanes,GL_SPECULAR,PGLFloat(@tmp));

  tmp:=ColorToGL(TeeMaterialAmbient);
  glMaterialfv(TeeColorPlanes,GL_AMBIENT,PGLFloat(@tmp));

//  tmp:=TeeGLColor(TeeMaterialEmission);
//  glMaterialfv(TeeColorPlanes,GL_EMISSION,PGLFloat(@tmp));

  Assert(CheckGLError,'Material '+IntToStr(ISavedError));
end;

{$IFNDEF LINUX}
Function TGLCanvas.GetDCHandle:HDC;
begin
  result:=FDC;
end;
{$ENDIF}

var
  FoundFormat : Integer=-1;

type
  TPixelFormatsArray=Array of Integer;

Function TGLCanvas.InitWindow( const DestCanvas:TCanvas;
                               const A3DOptions:TView3DOptions;
                               ABackColor:TColor;
                               Is3D:Boolean;
                               Const UserRect:TRect):TRect;

  function OpenGLColorDepth:Byte;
  begin
    if FColorDepth=cd24Bit then
       result:=24
    else
       result:=32;
  end;

  function CreateMultisampleContext(hDC:HDC; out PixelFormats:TPixelFormatsArray):Boolean;
  const
    MaxFormats=200;

  var IAttribCount:Integer;
      iAttributes : Array[0..23] of Integer;

    function AddAttribute(Attribute,Value:Integer):Integer;
    begin
      result:=IAttribCount;

      iAttributes[IAttribCount]:=Attribute;
      iAttributes[IAttribCount+1]:=Value;

      Inc(IAttribCount,2);
    end;

  var NumFormats  : Cardinal;
      tmpPixelFormats: Array[0..MaxFormats] of Integer;
      t           : Integer;
      fAttributes : Array[0..1] of Single;
      Samples_ARB_Attrib : Integer;
  begin
    result:=False;

    if Assigned(wglChoosePixelFormatARB) then
    begin
      Samples_ARB_Attrib:=-1;
      
      fAttributes[0]:=0;
      fAttributes[1]:=0;

      IAttribCount:=0;

      if IDrawToBitmap then
         AddAttribute(WGL_DRAW_TO_BITMAP_ARB,GL_TRUE)
      else
         AddAttribute(WGL_DRAW_TO_WINDOW_ARB,GL_TRUE);

      AddAttribute(WGL_SUPPORT_OPENGL_ARB,GL_TRUE);

      //if IDrawToBitmap then
      //   AddAttribute(WGL_SUPPORT_GDI_ARB,GL_TRUE);

      // Hardware acceleration not available when drawing to Bitmap Canvas context
      if not IDrawToBitmap then
         AddAttribute(WGL_ACCELERATION_ARB,WGL_FULL_ACCELERATION_ARB);

      AddAttribute(WGL_COLOR_BITS_ARB,OpenGLColorDepth);
      AddAttribute(WGL_ALPHA_BITS_ARB,8);
      AddAttribute(WGL_DEPTH_BITS_ARB,16);
      AddAttribute(WGL_STENCIL_BITS_ARB,1);

      // DoubleBuffer not available when drawing to Bitmap Canvas context
      if InternalUseBuffer then
         AddAttribute(WGL_DOUBLE_BUFFER_ARB,GL_TRUE)
      else
         AddAttribute(WGL_DOUBLE_BUFFER_ARB,GL_FALSE);

      //AddAttribute(WGL_DRAW_TO_PBUFFER_ARB, GL_TRUE);

      // MultiSample not available when drawing to Bitmap Canvas context
      if not IDrawToBitmap then
      begin
        AddAttribute(WGL_SAMPLE_BUFFERS_ARB,GL_TRUE);
        Samples_ARB_Attrib:=AddAttribute(WGL_SAMPLES_ARB,FAntialiasLevel);
      end;

      // NVidia:
      // __GL_FSAA_MODE
      // ?

      AddAttribute(WGL_PIXEL_TYPE_ARB,WGL_TYPE_RGBA_ARB);

      AddAttribute(0,0); // <-- Terminator signal

      if wglChoosePixelFormatARB(hDC,@iAttributes,@fAttributes,MaxFormats,@tmpPixelFormats,@NumFormats) then
      begin
         if NumFormats<1 then
         begin
           if Samples_ARB_Attrib=-1 then
              result:=False
           else
           begin
             // Second try, use 2 samples instead of FAntialiasLevel (default=4):

             // WGL_SAMPLES_ARB =2
             iAttributes[Samples_ARB_Attrib]:=2;

             if wglChoosePixelFormatARB(hDC,@iAttributes,@fAttributes,1,@tmpPixelFormats,@NumFormats) then
                result:=NumFormats>0;
           end;
         end
         else
           result:=True;
      end;

      if result then
      begin
        SetLength(PixelFormats,NumFormats);
        for t:=0 to NumFormats-1 do
            PixelFormats[t]:=tmpPixelFormats[t];
      end;
    end;
  end;

  procedure TryCreateNormalContext(ADC:HDC);
  begin
    // Try first to create a buffered context:
    if InternalUseBuffer then
       HRC:=CreateRenderingContext(ADC,[opDoubleBuffered],OpenGLColorDepth,1)
    else
       HRC:=0;

    // Second try, try to create a non-buffered context:
    if HRC=0 then
       HRC:=CreateRenderingContext(ADC,[],OpenGLColorDepth,1);
  end;

  procedure TryMultiSample;

    function CanSetFormat(const ColorDepth,AFormat:Integer):Boolean;
    var PFDescriptor : TPixelFormatDescriptor;
    begin
      // Debug:
      // if OpenGL2.DescribePixelFormat(GetDCHandle,AFormat,SizeOf(PFDescriptor),PFDescriptor) then

      if InternalUseBuffer then
         FillDescriptor(GetDCHandle,[opDoubleBuffered],ColorDepth,0,PFDescriptor)
      else
         FillDescriptor(GetDCHandle,[],ColorDepth,0,PFDescriptor);

      result:=OpenGL2.wglSetPixelFormat(GetDCHandle,AFormat,@PFDescriptor);
    end;

    procedure CheckARBExtensions;
    var tmpWin : TControl;
    begin
      if not Assigned(wglChoosePixelFormatARB) then
      begin
        tmpWin:=TWinControl.Create(nil);
        try
          tmpWin.Parent:=A3DOptions.Parent;
          with TControlCanvas.Create do
          try
            Control:=tmpWin;

            TryCreateNormalContext(Handle);

            if HRC>0 then
            begin
              DestroyRenderingContext(HRC);
              HRC:=0;
            end;
          finally
            Free;
          end;
        finally
          tmpWin.Free;
        end;
      end;
    end;

    function TrySetFormat(const AFormat:Integer):Boolean;
    begin
       if CanSetFormat(OpenGLColorDepth, AFormat) or
          CanSetFormat(24,AFormat) or
          CanSetFormat(32,AFormat) then
          result:=True
       else
          result:=False;
    end;

  var PixelFormats : TPixelFormatsArray;
      t            : Integer;
      tmpHRC       : HGLRC;
      Found        : Boolean;
      //PFDescriptor : TPixelFormatDescriptor;
  begin
    CheckARBExtensions;

    // Re-obtain FDC, because TCanvas CanvasList and BitmapCanvasList might
    // have destroyed (freed) the handle context.
    FDC:=DestCanvas.Handle;

    if FoundFormat=-1 then
       Found:=False
    else
    begin
      Found:=TrySetFormat(FoundFormat);

      if not Found then
         FoundFormat:=-1;
    end;

    if (not Found) and CreateMultiSampleContext(GetDCHandle,PixelFormats) then
    begin
      DestroyGLContext;

      t:=0;

      repeat
        Found:=TrySetFormat(PixelFormats[t]);

        if Found then
        begin
          FoundFormat:=PixelFormats[t];
          break;
        end;

        Inc(t);
      until t>=Length(PixelFormats);

      // Experimental: Force using first pixel format:
      //if (not Found) and (Length(PixelFormats)>0) then
      //begin
      //   OpenGL2.SetPixelFormat(GetDCHandle,PixelFormats[0],@PFDescriptor);
      //   Found:=True;
      //end;

      PixelFormats:=nil;
    end;

    if Found then
    begin
      tmpHRC:=OpenGL2.wglCreateContext(GetDCHandle);

      if tmpHRC=0 then
         FoundFormat:=-1
      else
         HRC:=tmpHRC;
    end;
  end;

var tmpMaxSize : Array[0..0] of Integer;
    Old : TView3DOptions;
begin
  FBounds:=UserRect;
  RectSize(Bounds,FWidth,FHeight);

  result:=UserRect;

  FX:=0;
  FY:=0;
  FIs3D:=Is3D;

  if Assigned(A3DOptions) then
     FontZoom:=A3DOptions.FontZoom;

  if (IDestCanvas<>DestCanvas) or (View3DOptions<>A3DOptions) then
  begin
    SetCanvas(DestCanvas);

    IDestCanvas:=DestCanvas;
    View3DOptions:=A3DOptions;

    DestroyGLContext;

    FDC:=DestCanvas.Handle;

    {$IFNDEF LINUX}

    IDrawToBitmap:=GetObjectType(FDC) = OBJ_MEMDC;

    // Always try to create a multisample (2x or 4x) antialiased context:
    // (* NO: if not (csDesigning in A3DOptions.Parent.ComponentState) then )

    // Inside TryMultiSample there's a call to "Handle" which forces a call to
    // TCustomTeePanel.CreateParams, which in turn resets our View3DOptions to nil,
    // so lets save and restore a copy here:
    Old:=View3DOptions;
    try
      TryMultiSample;

      if HRC=0 then
      begin
        if FColorDepth=cd24Bit then
           FColorDepth:=cd32Bit
        else
           FColorDepth:=cd24Bit;

        TryMultiSample;
      end;
    finally
      View3DOptions:=Old;
    end;

    // If its not possible to create antialiased, then try normal (without antialias)
    if HRC=0 then
    begin
      TryCreateNormalContext(GetDCHandle);

      if HRC=0 then
      begin
        if FColorDepth=cd24Bit then
           FColorDepth:=cd32Bit
        else
           FColorDepth:=cd24Bit;

        TryCreateNormalContext(GetDCHandle);
      end;
    end;

    // No way, we should exit.
    if HRC=0 then
    begin
      {$IFOPT C+}
      // We cannot assert here. glGetError always returns 1282 when no GL context is active
      // Assert(CheckGLError,'GL CreateContext '+IntToStr(ISavedError));
      {$ENDIF}

      // Crash
      Raise Exception.Create('Cannot create an OpenGL context.');
    end;

    ActivateRenderingContext(GetDCHandle,HRC);
    Assert(CheckGLError,'ActivateContext');
    {$ENDIF}

    glGetBooleanv(GL_ARB_MULTISAMPLE,@IAntiAlias);

    // Do not use Assert here. Just set IAntiAlias to False.
    if not CheckGLError then
       IAntiAlias:=False;

    SetPolygonMode;

    glEnable(GL_AUTO_NORMAL);
    Assert(CheckGLError,'AutoNormal');

    glEnable(GL_NORMALIZE); // GL_NORMALIZE vs. GL_RESCALE_NORMAL_EXT
    Assert(CheckGLError,'EnableNormalize');

    glEnable(GL_DEPTH_TEST);
    Assert(CheckGLError,'EnableDepth');

    glDepthFunc(GL_LESS);
    Assert(CheckGLError,'DepthFunc');

    glEnable(GL_COLOR_MATERIAL);
    Assert(CheckGLError,'EnableColorMaterial');

    glColorMaterial(TeeColorPlanes,GL_AMBIENT_AND_DIFFUSE);
    Assert(CheckGLError,'ColorMaterial');

    SetMaterialColor;

    ApplyPolygonOffset(TeePolygonOffset);

    glDisable(GL_CULL_FACE);

    // Dithering not necessary. Not all drivers do dithering in 24 or 32 bit color modes.
    // glEnable(GL_DITHER);
    // Assert(CheckGLError,'Dither');

    glHint(GL_PERSPECTIVE_CORRECTION_HINT, TeePerspectiveQuality);
    glHint(GL_LINE_SMOOTH_HINT, TeeSmoothQuality);
    glHint(GL_POINT_SMOOTH_HINT, TeeSmoothQuality);
    glHint(GL_POLYGON_SMOOTH_HINT, TeeSmoothQuality);

    glLightModelf(GL_LIGHT_MODEL_TWO_SIDE,TeeFullLightModel);
    glLightModelf(GL_LIGHT_MODEL_LOCAL_VIEWER,TeeLightLocal);

    {
    if GL_EXT_separate_specular_color then
       glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR);
    }

    Assert(CheckGLError,'LightModel');

    ICanNPOTS:=False; //GL_ARB_Texture_Rectangle;
    IRelaxedNPOTS:=GL_ARB_texture_non_power_of_two;

    glGetIntegerv(GL_MAX_TEXTURE_SIZE,@tmpMaxSize);
    IMaxTexSize:=tmpMaxSize[0];
  end
  else
  {$IFNDEF LINUX}
  if GetObjectType(GetDCHandle) <> OBJ_MEMDC then
  begin
    if FDC=0 then
       FDC:=DestCanvas.Handle;

    CheckContext;
  end
  {$ENDIF}
  ;

  BackColor:=ABackColor;
end;

procedure TGLCanvas.ApplyPolygonOffset(Enable:Boolean);
begin
  if Enable then
  begin
    //glEnable(GL_POLYGON_OFFSET_POINT);
    //glEnable(GL_POLYGON_OFFSET_LINE);

    glEnable(GL_POLYGON_OFFSET_FILL);
    glPolygonOffset(0.5,1);
  end
  else
    glDisable(GL_POLYGON_OFFSET_FILL);

  Assert(CheckGLError,'PolygonOffset');
end;

procedure TGLCanvas.CheckContext;
begin
  if (HRC<>0) and (HRC<>wglGetCurrentContext) then
  begin
    ActivateRenderingContext(GetDCHandle,HRC);
    Assert(CheckGLError,'ActivateContext');
  end;
end;

Procedure TGLCanvas.InitAmbientLight(AmbientLight:Integer);
var tmp    : GLMat;
    tmpNum : Single;
begin
  glDisable(GL_LIGHT0);
  glDisable(GL_LIGHT1);
  glDisable(GL_LIGHT2);
  Assert(CheckGLError,'DisableLight');

  glEnable(GL_LIGHTING);
  Assert(CheckGLError,'EnableLight');

  if AmbientLight>0 then
  begin
    tmpNum:=AmbientLight*0.01;
    tmp[0]:=tmpNum;
    tmp[1]:=tmpNum;
    tmp[2]:=tmpNum;
    tmp[3]:=1;

    glLightModelfv(GL_LIGHT_MODEL_AMBIENT,  PGLFloat(@tmp));
    Assert(CheckGLError,'LightModel');
  end
  else
  begin
    tmp[0]:=0;
    tmp[1]:=0;
    tmp[2]:=0;
    tmp[3]:=1;

    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, PGLFloat(@tmp));
    Assert(CheckGLError,'LightModel');

    glDisable(GL_LIGHTING);
    Assert(CheckGLError,'DisableLightModel');
  end;

  SetShininess(FShininess);
end;

procedure TGLCanvas.SetShininess(const Value:Single);
begin
  glMaterialf(TeeColorPlanes, GL_SHININESS, Value*128.0);
  Assert(CheckGLError,'SetShininess '+IntToStr(ISavedError));
end;

procedure TGLCanvas.InitLight(Num:Integer; const AAmbient,ADiffuse,ASpecular:GLMat;
                              const Position,Direction:TPoint3DFloat;
                              UseDirection:Boolean;
                              const SpotDegrees:Single;
                              SpotExponent:Integer);

  procedure AssertLight(const Text:String);
  begin
    Assert(CheckGLError,Text+' '+IntToStr(Num)+' '+IntToStr(ISavedError));
  end;

var tmp : GLMat;
begin
  glEnable(GL_LIGHTING);
  AssertLight('Enable Lighting');

  glEnable(Num);
  AssertLight('Enable Light '+IntToStr(Num));

  tmp:=AAmbient;
  glLightfv(Num,GL_AMBIENT, PGLFloat(@tmp));
  AssertLight('LightAmbient');

  tmp:=ADiffuse;
  glLightfv(Num,GL_DIFFUSE, PGLFloat(@tmp));
  AssertLight('LightDiffuse');

  tmp:=ASpecular;
  glLightfv(Num,GL_SPECULAR, PGLFloat(@tmp));
  AssertLight('LightSpecular');

  tmp[0]:=  Position.X;
  tmp[1]:= -Position.Y;
  tmp[2]:= -Position.Z;
  tmp[3]:=1;
  glLightfv(Num,GL_POSITION, PGLFloat(@tmp));
  AssertLight('LightPosition');

  if UseDirection then
  begin
    tmp[0]:=  Direction.X;
    tmp[1]:= -Direction.Y;
    tmp[2]:= -Direction.Z;
  end
  else
  begin
    tmp[0]:=0;
    tmp[1]:=0;
    tmp[2]:=1;
  end;

  tmp[3]:=0;

  glLightfv(Num,GL_SPOT_DIRECTION, PGLFloat(@tmp));
  AssertLight('LightDirection');

  if SpotDegrees=180 then
     glLightf(Num,GL_SPOT_CUTOFF,TeeDefaultLightSpot)
  else
     glLightf(Num,GL_SPOT_CUTOFF,SpotDegrees);

  glLighti(Num,GL_SPOT_EXPONENT,SpotExponent);

  //glLightf(Num, GL_CONSTANT_ATTENUATION, 0 );
  //glLightf(Num, GL_LINEAR_ATTENUATION , 0 );
  //glLightf(Num, GL_QUADRATIC_ATTENUATION , 0.0002 );

  AssertLight('LightSpot');
end;

Procedure TGLCanvas.ShowImage(const DestCanvas,DefaultCanvas:TCanvas; Const UserRect:TRect);
begin
  if Assigned(FOnFinish) then
     FOnFinish(Self);

  //glFinish;

  glFlush;
  Assert(CheckGLError,'Flush: '+IntToStr(ISavedError));

  {$IFNDEF LINUX}
  if InternalUseBuffer then
     OpenGL2.SwapBuffers(GetDCHandle);
  {$ENDIF}

  SetCanvas(DefaultCanvas);
  Assert(CheckGLError,'ShowImage');

  if IDrawToBitmap then
     DeleteTextures;

  FDirty:=False;
end;

Function TGLCanvas.ReDrawBitmap:Boolean;
begin
  result:=(not FDirty) and InternalUseBuffer;

  {$IFNDEF LINUX}
  if result then
     OpenGL2.SwapBuffers(GetDCHandle);
  {$ENDIF}
end;

function TGLCanvas.RenderToBitmap(var ABitmap:TBitmap; const R:TRect):Boolean;
begin
  result:=UseBuffer and EqualRect(R,TeeRect(FWidth,FHeight));

  if result then
  begin
    glReadBuffer(GL_FRONT);

    Assert(CheckGLError,'glReadBuffer: '+IntToStr(ISavedError));

    BufferToBitmap(ABitmap,R);
  end;
end;

procedure TGLCanvas.Rectangle(const X0,Y0,X1,Y1:Integer);
begin
  if Brush.Style<>bsClear then
     FillRect(TeeRect(X0,Y0,X1,Y1));

  if IsPenVisible then
  begin
    BeginPen(GL_LINE_LOOP);
      TeeVertex2D(X0,Y0);
      TeeVertex2D(X1,Y0);
      TeeVertex2D(X1,Y1);
      TeeVertex2D(X0,Y1);
    glEnd;
    EndPen;
  end;

  Assert(CheckGLError,'Rectangle');
end;

procedure TGLCanvas.MoveTo(const X, Y: Integer);
begin
  FX:=X;
  FY:=Y;
end;

procedure TGLCanvas.MoveTo(const P:TPointFloat);
begin
  FX:=P.X;
  FY:=P.Y;
end;

procedure TGLCanvas.Pyramid(Vertical:Boolean; Left,Top,Right,Bottom,z0,z1:Integer; DarkSides:Boolean);
var AWidth,
    AHeight,
    ADepth:Integer;
begin
  glPushMatrix;

  glEnable(GL_CULL_FACE);

  if Vertical then
  begin
    if Left>Right then SwapInteger(Left,Right);
    if Top>Bottom then glDisable(GL_CULL_FACE);
  end
  else
  begin
    if Top>Bottom then SwapInteger(Top,Bottom);
    if Left>Right then glDisable(GL_CULL_FACE);
  end;

  if z0>z1 then SwapInteger(z0,z1);

  glTranslatef(Left,-Bottom,-z0);

  if Vertical then
  begin
    AWidth:=Right-Left;
    AHeight:=Top-Bottom;
  end
  else
  begin
    AWidth:=Bottom-Top;
    AHeight:=Right-Left;
    glRotatef(90,0,0,1);
  end;

  ADepth:=z1-z0;

  if Brush.Style<>bsClear then
  begin
    BeginBrush(GL_TRIANGLE_FAN);

    TeeNormal(AWidth div 2,-AHeight,ADepth div 2);
    glTexCoord3d(0.5,1,0.5);
    TeeVertex3D(AWidth div 2,AHeight,ADepth div 2);

    TeeNormal(0,0,-1);
    glTexCoord3i(0,0,0);
    TeeVertex3D(0,0,0);

    TeeNormal(1,0,-1);
    glTexCoord3i(1,0,0);
    TeeVertex3D(AWidth,0,0);

    TeeNormal(1,0,1);
    glTexCoord3i(1,0,1);
    TeeVertex3D(AWidth,0,ADepth);

    TeeNormal(0,0,1);
    glTexCoord3i(0,0,1);
    TeeVertex3D(0,0,ADepth);

    TeeNormal(0,0,-1);
    glTexCoord3i(0,0,0);
    TeeVertex3D(0,0,0);

    glEnd;

    RectangleY(0,0,AWidth,0,ADepth);

    EndBrushBitmap;
  end;

  glDisable(GL_CULL_FACE);

  if IsPenVisible then
  begin
    BeginPen(GL_LINE_LOOP);
      TeeVertex2D(0,0);
      TeeVertex2D(AWidth,0);
      TeeVertex3D(AWidth,0,ADepth);
      TeeVertex3D(0,0,ADepth);
    glEnd;

    glBegin(GL_LINE_STRIP);
    TeeVertex2D(0,0);
    TeeVertex3D(AWidth div 2,AHeight,ADepth div 2);
    TeeVertex3D(0,0,ADepth);
    glEnd;

    glBegin(GL_LINE_STRIP);
    TeeVertex2D(AWidth,0);
    TeeVertex3D(AWidth div 2,AHeight,ADepth div 2);
    TeeVertex3D(AWidth,0,ADepth);
    glEnd;
    EndPen;
  end;

  glPopMatrix;
  Assert(CheckGLError,'Pyramid');
end;

procedure TGLCanvas.Tetrahedron(Left,Top,Right,Bottom,z0,z1:Single);
var AWidth,
    AHeight,
    ADepth : Single;
begin
  glEnable(GL_CULL_FACE);

  if Left>Right then SwapSingle(Left,Right);
  if Top>Bottom then SwapSingle(Top,Bottom);
  if z0>z1 then SwapSingle(z0,z1);

  glPushMatrix;
  glTranslatef(Left,-Bottom,-z0);

  AWidth:=Right-Left;
  AHeight:=Top-Bottom;
  ADepth:=z1-z0;

  if Brush.Style<>bsClear then
  begin
    BeginBrush(GL_TRIANGLE_FAN);

    TeeNormal(0,0,-1);
    glTexCoord3d(0,0,0);
    TeeVertex3D(0,0,0);

    TeeNormal(1,0,1);
    glTexCoord3d(ITextureRepeatX,0,1);
    TeeVertex3D(AWidth,0,ADepth);

    TeeNormal(1,1,-1);
    glTexCoord3d(ITextureRepeatX,ITextureRepeatY,0);
    TeeVertex3D(AWidth,AHeight,0);

    TeeNormal(0,1,1);
    glTexCoord3d(0,ITextureRepeatY,1);
    TeeVertex3D(0,AHeight,ADepth);

    glEnd;

    glBegin(GL_TRIANGLE_FAN);

    TeeNormal(0,0,-1);
    glTexCoord3d(0,0,0);
    TeeVertex3D(0,0,0);

    TeeNormal(1,1,-1);
    glTexCoord3d(ITextureRepeatX,ITextureRepeatY,0);
    TeeVertex3D(0,AHeight,ADepth);

    TeeNormal(1,0,1);
    glTexCoord3d(ITextureRepeatX,0,1);
    TeeVertex3D(AWidth,0,ADepth);

    glEnd;

    glBegin(GL_TRIANGLE_FAN);

    TeeNormal(0,0,-1);
    glTexCoord3d(0,0,0);
    TeeVertex3D(AWidth,AHeight,0);

    TeeNormal(1,1,-1);
    glTexCoord3d(ITextureRepeatX,ITextureRepeatY,0);
    TeeVertex3D(AWidth,0,ADepth);

    TeeNormal(1,0,1);
    glTexCoord3d(ITextureRepeatX,0,1);
    TeeVertex3D(0,AHeight,ADepth);

    glEnd;

    EndBrushBitmap;
  end;

  glDisable(GL_CULL_FACE);

  if IsPenVisible then
  begin
    BeginPen(GL_LINE_STRIP);
      TeeVertex3D(0,0,0);
      TeeVertex3D(AWidth,AHeight,0);
      TeeVertex3D(AWidth,0,ADepth);
      TeeVertex3D(0,AHeight,ADepth);
      TeeVertex3D(0,0,0);
      TeeVertex3D(AWidth,0,ADepth);
    glEnd;

    glBegin(GL_LINES);
      TeeVertex3D(0,AHeight,ADepth);
      TeeVertex3D(AWidth,AHeight,0);
    glEnd;
    EndPen;
  end;

  glPopMatrix;
  Assert(CheckGLError,'Tetrahedron');
end;

function TGLCanvas.QuadricDrawStyle:Integer;
begin
  case DrawStyle of
    tcsWire: result:=GLU_LINE;
     tcsDot: result:=GLU_POINT;
  else
  // tcsSolid:
    result:=GLU_FILL;
  end;
end;

procedure TGLCanvas.InternalCylinder(Vertical:Boolean;
                     Left,Top,Right,Bottom,Z0,Z1:Integer; Dark3D:Boolean;
                     ConePercent:Integer;
                     const Side1:TChartBrush=nil;
                     const Side2:TChartBrush=nil);
var tmpSize,
    tmp,
    tmp2,
    Radius  : Integer;
    HasSide1,
    HasSide2,
    Inverse : Boolean;
begin
  Inverse:=False;

  glPushMatrix;

  Radius:=Abs(Z1-Z0) div 2;

  if Left>Right then
  Begin
    Inverse:=True;
    SwapInteger(Left,Right);
  end;

  if Top>Bottom then
  Begin
    Inverse:=True;
    SwapInteger(Top,Bottom);
  end;

  if z0>z1 then
     SwapInteger(z0,z1);

  if Vertical then
  begin
    Radius:=MinInteger((Right-Left) div 2,Radius);

    if Inverse then
    Begin
      glTranslatef((Left+Right) div 2,-Bottom,-(z0 + z1) div 2);
      glRotatef(270, 1, 0, 0);
    end
    else
    Begin
      glTranslatef((Left+Right) div 2,-Top,-(z0+z1) div 2);
      glRotatef(90,1,0,0);
    end;

    tmpSize:=Bottom-Top;
  end
  else
  begin
    Radius:=MinInteger((Bottom-Top) div 2,Radius);

    if Inverse then
    Begin
      glTranslatef(Left,-(Top+Bottom) div 2,-(z0+z1) div 2);
      glRotatef(90,0,1,0);
    end
    else
    Begin
      glTranslatef(Right,-(Top+Bottom) div 2,-(z0+z1) div 2);
      glRotatef(270,0,1,0);
    end;

    tmpSize:=Right-Left;
  end;

  if ConePercent=100 then tmp:=Radius
                     else tmp:=Round(0.01*ConePercent*Radius);

  tmp2:=Math.Min(TeeNumCylinderSides,6*Radius);

  if Brush.Style<>bsClear then
  begin
    HasSide1:=(not Assigned(Side1)) or (Side1.Style<>bsClear);
    HasSide2:=(not Assigned(Side2)) or (Side2.Style<>bsClear);

    if HasSide1 and HasSide2 then
       glEnable(GL_CULL_FACE);

    SetColor(Brush.Color);
    SetBrushBitmap;

    gluCylinder(Quadric,tmp,Radius,tmpSize,tmp2,TeeCylinderStacks);

    EndBrushBitmap;

    if ConePercent<>0 then
    begin
      if HasSide1 then
      begin
        if Assigned(Side1) then
        begin
          SetColor(Side1.Color);
          SetBrushBitmap(Side1.Bitmap);
        end
        else
        begin
          SetColor(Brush.Color);
          SetBrushBitmap;
        end;

        gluQuadricOrientation(Quadric, GLU_INSIDE);
        gluDisk(Quadric,0,tmp,tmp2,TeeCylinderStacks);
        gluQuadricOrientation(Quadric, GLU_OUTSIDE);

        EndBrushBitmap;
      end;
    end;

    if HasSide2 then
    begin
      if Assigned(Side2) then
      begin
        SetColor(Side2.Color);
        SetBrushBitmap(Side2.Bitmap);
      end
      else
      begin
        SetColor(Brush.Color);
        SetBrushBitmap;
      end;

      glPushMatrix;
      glTranslatef(0,0,tmpSize);
      gluDisk(Quadric,0,Radius,tmp2,TeeCylinderStacks);
      glPopMatrix;

      EndBrushBitmap;
    end;

    glDisable(GL_CULL_FACE);
  end;

  if IsPenVisible then
  begin
    SetPen(Pen);
    SetColor(Pen.Color);
    gluQuadricDrawStyle(Quadric, GLU_LINE);
    gluCylinder(Quadric,tmp+0.5,Radius+0.5,tmpSize+0.5,tmp2,6);
    gluQuadricDrawStyle(Quadric, QuadricDrawStyle);
    EndPen;
  end;

  glPopMatrix;

  Assert(CheckGLError,'InternalCylinder: '+IntToStr(ISavedError));
end;

procedure TGLCanvas.Cylinder(Vertical:Boolean; Left,Top,Right,Bottom,Z0,Z1:Integer; DarkCover:Boolean);
begin
  InternalCylinder(Vertical,Left,Top,Right,Bottom,Z0,Z1,DarkCover,100);
  Assert(CheckGLError,'Cylinder');
end;

procedure TGLCanvas.Cone(Vertical:Boolean; Left,Top,Right,Bottom,Z0,Z1:Integer; Dark3D:Boolean; ConePercent:Integer);
begin
  InternalCylinder(Vertical,Left,Top,Right,Bottom,Z0,Z1,Dark3D,ConePercent);
  Assert(CheckGLError,'Cone');
end;

procedure TGLCanvas.Sphere(const x,y,z,Radius:Single);
var Center,
    R       : TPoint3DFloat;
begin
  Center.X:=X;
  Center.Y:=Y;
  Center.Z:=Z;

  R.X:=Radius;
  R.Y:=Radius;
  R.Z:=Radius;

  Sphere(Center,R);
end;

procedure TGLCanvas.Sphere(x,y,z:Integer; const Radius:Double);
begin
  Sphere(1.0*x,y,z,Radius);
end;

procedure TGLCanvas.Sphere(const Center,Radius:TPoint3DFloat);
begin
  glPushMatrix;
  glTranslatef(Center.x,-Center.y,-Center.z);
  glScalef(Radius.X,Radius.Y,Radius.Z);

  if Brush.Style<>bsClear then
  begin
    SetColor(Brush.Color);
    SetBrushBitmap;

    glEnable(GL_CULL_FACE);
    gluSphere(Quadric,1,TeeSphereSlices,TeeSphereStacks);
    glDisable(GL_CULL_FACE);

    EndBrushBitmap;
  end;

  if IsPenVisible then
  begin
    SetPen(Pen);
    SetColor(Pen.Color);

    gluQuadricDrawStyle(Quadric, GLU_LINE);
    gluSphere(Quadric,1,TeeSphereSlices,TeeSphereStacks);
    gluQuadricDrawStyle(Quadric, QuadricDrawStyle);
    EndPen;
  end;

  glPopMatrix;
  Assert(CheckGLError,'Sphere');
end;

procedure TGLCanvas.StretchDraw(const Rect:TRect; const Graphic: TGraphic;
                          const Pos:Double; Plane:TCanvas3DPlane=cpZ);
var Old : Boolean;
begin
  if Assigned(Graphic) then
    if Plane=cpZ then
    begin
      Old:=TeeWrapTextures;
      TeeWrapTextures:=False;

      if SetBrushBitmap(Graphic) then
      begin
        //glMatrixMode(GL_TEXTURE);
        //glPushMatrix;
        //glScalef(1/Graphic.Width,-1/Graphic.Height,1);
        //glMatrixMode(GL_MODELVIEW);

        glDisable(GL_TEXTURE_GEN_S);
        glDisable(GL_TEXTURE_GEN_T);

        glBegin(GL_QUADS);
        SetColor(clWhite);

        InternalRectangle(Rect,Pos);

        EndBrushBitmap(Graphic);

        //glMatrixMode(GL_TEXTURE);
        //glPopMatrix;
        //glMatrixMode(GL_MODELVIEW);

        ITextureRepeatY:=-ITextureRepeatY;
      end;

      TeeWrapTextures:=Old;

      Assert(CheckGLError,'StretchDraw');
    end
    else
      inherited StretchDraw(Rect,Graphic,Pos,Plane);
end;

Procedure TGLCanvas.SetColor(const AColor:TColor); 
var tmp : GLMat;
begin
  ColorToGL(AColor,tmp);
  tmp[3]:=ITransp*tmp[3];

  if (IOldColor.Color<>AColor) or (IOldColor.Transp<>tmp[3]) then
  begin
    glColor4fv(PGLFloat(@tmp));

    IPenAlpha:=(tmp[3]<1) and (not IActiveBlend);

    if IPenAlpha then
       ActivateBlend(True);

    IOldColor.Color:=AColor;
    IOldColor.Transp:=tmp[3];
  end;
end;

procedure TGLCanvas.BeginPen;
begin
  SetPen(Pen);
  SetColor(Pen.Color);
end;

procedure TGLCanvas.BeginPen(const Mode:GLEnum);
begin
  BeginPen(Mode,Pen);
end;

procedure TGLCanvas.BeginPen(const Mode:GLEnum; const APen:TTeePen);
begin
  SetPen(APen);
  SetColor(APen.Color);

  // Important: glBegin must be done *after* SetColor (GL_BLEND)
  glBegin(Mode);
end;

procedure TGLCanvas.EndPen;
begin
  if IPenAlpha then
     ActivateBlend(False);
end;

procedure TGLCanvas.DoChangedBrush(const ABrush:TTeeBrush);
begin
end;

{
procedure TGLCanvas.DoChangedFont;
begin
end;
}

procedure TGLCanvas.DoChangedPen;
begin
end;

procedure TGLCanvas.SetPen(const APen:TTeePen);
const
  ByteToTransp=1/2.55;
begin
  with APen do
  begin
    // Avoid redundant state change (repeated glLineStipple)
    if Style<>IOldPenStyle then
    begin
      if Style=psSolid then
         glDisable(GL_LINE_STIPPLE)
      else
      begin
        glEnable(GL_LINE_STIPPLE);

        case Style of
         psSolid   : glLineStipple(1,$FFFF);
         psDot     : glLineStipple(1,$5555);
         psDash    : glLineStipple(1,$00FF);
         psDashDot : glLineStipple(1,$55FF);
        else
         glLineStipple(1,$1C47);
        end;
      end;

      IOldPenStyle:=Style;
    end;

    // Avoid redundant state change (repeated glLineWidth with same value)
    if IOldPenWidth<>Width then
    begin
      IOldPenWidth:=Max(0.1,Width);
      glLineWidth(IOldPenWidth);
    end;
  end;

  Assert(CheckGLError,'SetPen');
end;

// Called from TFastLineSeries with "FastPen:=True".
// When activating, disable antialias and start a LINE_STRIP.
// When deactivating, end LINE_STRIP and restore antialias.
procedure TGLCanvas.SetFastLine(const Active:Boolean);
begin
  if not FIs3D then
    if Active then
    begin
      SetAntiAlias(False);
      BeginPen(GL_LINE_STRIP);
      glVertex2f(FX,-FY);
    end
    else
    begin
      glEnd;
      EndPen;
      SetAntiAlias(True);
    end;
end;

procedure TGLCanvas.FastLineTo(const X, Y: Integer);
begin
  TeeVertex2D(X,Y);
end;

procedure TGLCanvas.LineTo(const X, Y: Single);
begin
  BeginPen(GL_LINES);
    glVertex2f(FX,-FY);
    glVertex2f(X,-Y);
  glEnd;
  EndPen;

  Assert(CheckGLError,'LineTo Float');

  FX:=X;
  FY:=Y;
end;

procedure TGLCanvas.LineTo(const X, Y: Integer);
begin
  BeginPen(GL_LINES);
    glVertex2f(FX,-FY);
    TeeVertex2D(X,Y);
  glEnd;
  EndPen;

  Assert(CheckGLError,'LineTo');

  FX:=X;
  FY:=Y;
end;

procedure TGLCanvas.ClipRectangle(Const Rect:TRect);
var tmp,
    tmpMidX,
    tmpMidY : Single;
begin
  tmp:=CalcZoom*0.005;

  tmpMidX:=FWidth*0.5;
  tmpMidY:=FHeight*0.5;

  glEnable(GL_SCISSOR_TEST);
  glScissor(Round(Abs(tmpMidX+tmp*Rect.Left-tmp*tmpMidX)),
            Round(Abs(FHeight-tmpMidY-tmp*Rect.Bottom+tmp*tmpMidY)),
            Round(tmp*Abs(Rect.Right-Rect.Left)),
            Round(tmp*Abs(Rect.Bottom-Rect.Top)));

  Assert(CheckGLError,'glScissor: '+IntToStr(ISavedError));
end;

procedure TGLCanvas.UnClipRectangle;
begin
  glDisable(GL_SCISSOR_TEST);
end;

Function TGLCanvas.GetPixel(x,y:Integer):TColor;
begin
  result:=clWhite; // How to do this in OpenGL ?
end;

Procedure TGLCanvas.DeleteTexture(Index:Integer); 
var Nums : Array[0..0] of GLUInt;
begin
  Nums[0]:=ITextures[Index].GLTexture;
  glDeleteTextures(1,@Nums);

  ITextures[Index].Graphic:=nil;
  ITextures[Index].GLTexture:=0;
end;

Procedure TGLCanvas.DeleteTextures;
var t : Integer;
begin
  for t:=0 to Length(ITextures)-1 do
      DeleteTexture(t);

  ITextures:=nil;
end;

procedure TGLCanvas.RemoveTexture(const AGraphic: TGraphic);
var t : Integer;
begin
  for t:=0 to Length(ITextures)-1 do
  if ITextures[t].Graphic=AGraphic then
  begin
    DeleteTexture(t);
    break;
  end;
end;

function TGLCanvas.GetTexQuality:Boolean;
begin
  result:=FTextureQuality=GL_LINEAR;
end;

procedure TGLCanvas.SetTexQuality(Value:Boolean);
begin
  if Value then
     FTextureQuality:=GL_LINEAR
  else
     FTextureQuality:=GL_NEAREST;
end;

function TGLCanvas.GetSync:TGLScreenSync;
begin
  if Assigned(wglGetSwapInterval) then
     if wglGetSwapInterval=1 then
        result:=ssYes
     else
        result:=ssNo
  else
     result:=ssSystem;
end;

procedure TGLCanvas.SetSync(const Value:TGLScreenSync);
begin
  if Assigned(wglSwapInterval) then
     if Value=ssYes then
        wglSwapInterval(1)
     else
     if Value=ssNo then
        wglSwapInterval(0);
end;

procedure TGLCanvas.SetTextureParams;
var tmpMode : Integer;
    tmp     : Integer;
begin
  glPixelStorei(GL_UNPACK_ALIGNMENT, 4);

  if TeeWrapTextures and (not ICanNPOTS) then
     tmpMode:=GL_REPEAT
  else
     tmpMode:=GL_CLAMP;

  glTexParameteri(GLTextureStyle, GL_TEXTURE_WRAP_S, tmpMode);
  Assert(CheckGLError,'TexParam1: '+IntToStr(ISavedError));

  glTexParameteri(GLTextureStyle, GL_TEXTURE_WRAP_T, tmpMode);
  Assert(CheckGLError,'TexParam2');

  tmp:=FTextureQuality;

  glTexParameteri(GLTextureStyle, GL_TEXTURE_MIN_FILTER, tmp);
  Assert(CheckGLError,'TexParam3');

  glTexParameteri(GLTextureStyle, GL_TEXTURE_MAG_FILTER, tmp);
  Assert(CheckGLError,'TexParam4');

  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, TeeTextureEnvMode);
  Assert(CheckGLError,'TexEnv');

  //glTexEnvf(GL_TEXTURE_FILTER_CONTROL_EXT,GL_TEXTURE_LOD_BIAS_EXT,TeeLODBias);
  //Assert(CheckGLError,'TexEnv LOAD Bias');
end;

function TGLCanvas.AddNewTexture(AGraphic:TGraphic; AGLTexture:GLuint):TGraphic; 
var
  tmpW : Integer;
  tmpH : Integer;

  procedure UseBitmapBits(const ABitmap:TBitmap);  
  var Old  : TNotifyEvent;
      Bits : PByteArray;
      tmpFormat : GLenum;
  begin
    case ABitmap.PixelFormat of
         pf24Bit,
         pf32Bit: ;
    else
    begin
      Old:=ABitmap.OnChange;
      ABitmap.OnChange:=nil;
      ABitmap.PixelFormat:=pf32bit;
      ABitmap.OnChange:=Old;
    end;
    end;

    Bits:=ABitmap.ScanLine[tmpH-1];

    if ABitmap.PixelFormat=pf24Bit then
       tmpFormat:=GL_BGR
    else
       tmpFormat:=GL_BGRA;

    glTexImage2D(GLTextureStyle, 0, 4, tmpW, tmpH, 0, tmpFormat, GL_UNSIGNED_BYTE, Bits);
//    gluBuild2DMipmaps(GLTextureStyle, 4, tmpW, tmpH, tmpFormat, GL_UNSIGNED_BYTE, Bits);
  end;

  function InternalGetPicBits(var Bits:PByteArray):Boolean; 
  var tmpBitmap : TBitmap;
      tmpTransp : Integer;
      IsAlphaTexture : Boolean;
      IHasTransp  : Boolean;
      TranspColor : TRGB;
      Lines     : TRGBArray;
      tmpPos    : PByte;
      t,
      tt : Integer;
      tmpLine : PRGBs;
      Old     : TNotifyEvent;
  begin
    result:=False;
    
    if AGraphic is TBitmap then
       tmpBitmap:=TBitmap(AGraphic)
    else
    begin
      tmpBitmap:=TBitmap.Create;

      if AGraphic is TMetafile then
      begin
        tmpBitmap.Width:=AGraphic.Width;
        tmpBitmap.Height:=AGraphic.Height;
        tmpBitmap.Canvas.Draw(0,0,AGraphic);
      end
      else
        tmpBitmap.Assign(AGraphic);

      tmpBitmap.Transparent:=AGraphic.Transparent;
    end;

    tmpTransp:=Round(255*ITransp);

    IsAlphaTexture:=AGraphic is TAlphaBitmap;

    IHasTransp:=tmpBitmap.Transparent;

    if IHasTransp or IsAlphaTexture then
       TranspColor:=RGBValue(tmpBitmap.TransparentColor);

    if IHasTransp or IsAlphaTexture then
    begin
      if tmpBitmap=AGraphic then
      begin
        Old:=AGraphic.OnChange;
        AGraphic.OnChange:=nil;
        TeeCalcLines(Lines,tmpBitmap);
        AGraphic.OnChange:=Old;
      end
      else
        TeeCalcLines(Lines,tmpBitmap);

      GetMem(Bits,tmpH*tmpW*4);

      result:=True;

      try
        tmpPos:=@Bits[0];

        if IsAlphaTexture then
        begin
          for t:=0 to tmpH-1 do
          begin
            tmpLine:=Lines[tmpH-t-1];

            for tt:=0 to tmpW-1 do
            begin
              {$R-}
              with tmpLine[tt] do
              begin
                tmpPos^:=Red;
                Inc(tmpPos);

                tmpPos^:=Green;
                Inc(tmpPos);

                tmpPos^:=Blue;
                Inc(tmpPos);

                tmpPos^:=255-Red;  // (R+G+B) div 3
                Inc(tmpPos);
              end;
            end;
          end;
        end
        else
        begin
          for t:=0 to tmpH-1 do
          begin
            tmpLine:=Lines[t];

            for tt:=0 to tmpW-1 do
            begin
              {$R-}
              with tmpLine[tt] do
              begin
                tmpPos^:=Red;
                Inc(tmpPos);

                tmpPos^:=Green;
                Inc(tmpPos);

                tmpPos^:=Blue;
                Inc(tmpPos);

                if IHasTransp and
                   (Red=TranspColor.Red) and (Green=TranspColor.Green) and
                   (Blue=TranspColor.Blue) then
                     tmpPos^:=0
                else
                  tmpPos^:=tmpTransp;

                Inc(tmpPos);
              end;
            end;
          end;
        end;

      finally
        Lines:=nil;
      end;
    end
    else
      UseBitmapBits(tmpBitmap);

    if not (AGraphic is TBitmap) then
       tmpBitmap.Free;
  end;

var tmpW2,
    tmpH2 : Integer;
    tmpNew  : Boolean;
    tmpOld  : TGraphic;

    Bits : PByteArray;
    tmpFreeBits : Boolean;
begin
  tmpW:=AGraphic.Width;
  tmpH:=AGraphic.Height;

  if ICanNPOTS or IRelaxedNPOTS then
  begin
    tmpNew:=False;
    tmpW2:=0;
    tmpH2:=0;
  end
  else
  begin
    tmpW2:=Round(Power(2,Round(Log2(tmpW))));
    tmpH2:=Round(Power(2,Round(Log2(tmpH))));

    tmpNew:=(tmpW2<>tmpW) or (tmpH2<>tmpH);
  end;

  if tmpNew then
  begin
    tmpW:=tmpW2;
    tmpH:=tmpH2;

    tmpOld:=AGraphic;

    if AGraphic is TAlphaBitmap then
       AGraphic:=TAlphaBitmap.Create
    else
       AGraphic:=TBitmap.Create;

    TeeSetBitmapSize(TBitmap(AGraphic),tmpW,tmpH);

    with TBitmap(AGraphic) do
    begin
      PixelFormat:=TeePixelFormat;
      Canvas.StretchDraw(TeeRect(tmpW,tmpH),tmpOld);
      Transparent:=tmpOld.Transparent;

      if tmpOld is TBitmap then
      begin
        TransparentMode:=TBitmap(tmpOld).TransparentMode;
        TransparentColor:=TBitmap(tmpOld).TransparentColor;
      end;
    end;

    result:=tmpOld;
  end
  else
    result:=AGraphic;

  {$IFNDEF LINUX}
  glBindTexture(GLTextureStyle, AGLTexture);
  Assert(CheckGLError,'BinTexture');
  {$ENDIF}

  SetTextureParams;

  if (not tmpNew) and (AGraphic is TBitmap) and (not AGraphic.Transparent) then
     UseBitmapBits(TBitmap(AGraphic))
  else
  begin
    if (not Assigned(FOnGetPicBits)) or (not FOnGetPicBits(AGraphic,Bits)) then
       tmpFreeBits:=InternalGetPicBits(Bits)
    else
       tmpFreeBits:=True;

    if tmpFreeBits then
    try
      glTexImage2D(GLTextureStyle, 0, 4, tmpW, tmpH, 0, GL_RGBA,
                   GL_UNSIGNED_BYTE, Bits);

      Assert(CheckGLError,'TextImage2D');

      //glEnable(GL_TEXTURE_2D);
      //glGenerateMipmapEXT(GL_TEXTURE_2D);
    finally
      Dispose(Bits);
    end;
  end;

  if tmpNew then
     AGraphic.Free;
end;

Function TGLCanvas.FindTexture(const AGraphic:TGraphic; out AGLTexture:GLuint):Boolean; 
var t : Integer;
    Textures : Integer;
begin
  Textures:=Length(ITextures);

  for t:=0 to Textures-1 do
  with ITextures[t] do
  if Graphic=AGraphic then
  begin
    AGLTexture:=GLTexture;
    result:=True;
    exit;
  end;

  if (AGraphic.Width<=IMaxTexSize) and (AGraphic.Height<=IMaxTexSize) then
  begin
    SetLength(ITextures,Textures+1);

    glGenTextures(1, @ITextures[Textures].GLTexture);
    Assert(CheckGLError,'GenTextures');
    Assert(ITextures[Textures].GLTexture>0,'GenTextures GLTexture <= 0');

    AGLTexture:=ITextures[Textures].GLTexture;

    ITextures[Textures].Graphic:=AddNewTexture(AGraphic,AGLTexture);

    result:=True;
  end
  else
    result:=False;
end;

procedure TGLCanvas.StretchDraw(const Rect: TRect; const Graphic: TGraphic);
begin
  StretchDraw(Rect,Graphic,0);
end;

procedure TGLCanvas.Draw(X, Y: Integer; const Graphic: TGraphic);
begin
  if Assigned(Graphic) then
     StretchDraw(TeeRect(X,Y,X+Graphic.Width,Y+Graphic.Height),Graphic);
end;

Procedure TGLCanvas.GradientFill( Const Rect:TRect;
                                  StartColor,EndColor:TColor;
                                  Direction:TGradientDirection;
                                  Balance:Integer=50;
                                  RadialX:Integer=0;
                                  RadialY:Integer=0);
var P : TFourPoints;
    tmp : TTeeGradientColors;
begin
  with Rect do
  begin
    P[0]:=TopLeft;
    P[1]:=TeePoint(Right,Top);
    P[2]:=BottomRight;
    P[3]:=TeePoint(Left,Bottom);
  end;

  tmp:=TTeeGradientColors.Create(nil,TTeeGradientColor);
  try
    tmp.Add(0,StartColor);
    tmp.Add(1,EndColor);

    InternalGradient(P,tmp,Direction,Balance,RadialX,RadialY);
  finally
    tmp.Free;
  end;
end;

Procedure TGLCanvas.InternalGradient(const R:TRect; const Z:Single);
var P : TFourPoints;
begin
  with R do
  begin
    P[0]:=TopLeft;
    P[1]:=TeePoint(Right,Top);
    P[2]:=BottomRight;
    P[3]:=TeePoint(Left,Bottom);
  end;

  with Gradient do
       InternalGradient(P,Colors,Direction,Balance,RadialX,RadialY,Z);
end;

// Pending: Support multiple Gradient.Colors array for Diagonal and Radial directions
Procedure TGLCanvas.InternalGradient( const P:TFourPoints;
                                  const Colors:TTeeGradientColors;
                                  Direction:TGradientDirection;
                                  Balance:Integer=50;
                                  RadialX:Integer=0;
                                  RadialY:Integer=0;
                                  const Z:Single=0);

  Procedure DoLinear(StartToEnd,IsVertical:Boolean);
  var
    tmpCount : Integer;
    tmpSize : Single;

    procedure DrawPart(const Index:Integer; Inverted:Boolean);
    var tmpPos,
        tmpOff : Single;
        tmp : TTeeGradientColor;
    begin
      // Pending: glEnd, BeginBlending etc when Color has transparency

      if StartToEnd then
         tmp:=Colors[Index]
      else
         tmp:=Colors[tmpCount-Index];

      SetColor(tmp.Color);

      if StartToEnd then
         tmpOff:=tmp.Offset
      else
         tmpOff:=1-tmp.Offset;

      if IsVertical then
         tmpPos:=P[1].Y
      else
         tmpPos:=P[0].X;

      tmpPos:=tmpPos+(tmpSize*tmpOff);

      if IsVertical then
      begin
        if Inverted then
        begin
          TeeVertex3D(P[0].X,tmpPos,Z);
          TeeVertex3D(P[1].X,tmpPos,Z);
        end
        else
        begin
          TeeVertex3D(P[1].X,tmpPos,Z);
          TeeVertex3D(P[0].X,tmpPos,Z);
        end;
      end
      else
      begin
        if Inverted then
        begin
          TeeVertex3D(tmpPos,P[1].Y,Z);
          TeeVertex3D(tmpPos,P[2].Y,Z);
        end
        else
        begin
          TeeVertex3D(tmpPos,P[2].Y,Z);
          TeeVertex3D(tmpPos,P[1].Y,Z);
        end;
      end;
    end;

  var t : Integer;
  begin
    tmpCount:=Colors.Count-1;

    if IsVertical then
       tmpSize:=P[2].Y-P[1].Y
    else
       tmpSize:=P[1].X-P[0].X;

    for t:=0 to tmpCount div 2 do
    begin
      DrawPart(t,False);
      DrawPart(t+1,True);
    end;

    if tmpCount mod 2=1 then
    begin
      DrawPart(tmpCount-1,False);
      DrawPart(tmpCount,True);
    end;
  end;

  Procedure DoDiagonal(StartToEnd:Boolean);

    procedure SetMidColor(const A,B:TColor); 
    var tmpA, tmpB : GLMat;
    begin
      ColorToGL(A,tmpA);
      ColorToGL(B,tmpB);

      tmpA[0]:=(tmpA[0]+tmpB[0])*0.5;
      tmpA[1]:=(tmpA[1]+tmpB[1])*0.5;
      tmpA[2]:=(tmpA[2]+tmpB[2])*0.5;
      tmpA[3]:=(tmpA[3]+tmpB[3])*0.5;

      glColor4fv(PGLFloat(@tmpA));
    end;

  var tmp,
      tmpA,
      tmpB : TColor;
  begin
    tmpA:=Colors[0].Color;
    tmpB:=Colors[Colors.Count-1].Color;

    if not StartToEnd then
    begin
      tmp:=tmpA;
      tmpA:=tmpB;
      tmpB:=tmp;
    end;

    SetColor(tmpB);
    TeeVertex3D(P[2],Z);
    SetMidColor(tmpB,tmpA);
    TeeVertex3D(P[1],Z);
    SetColor(tmpA);
    TeeVertex3D(P[0],Z);
    SetMidColor(tmpB,tmpA);
    TeeVertex3D(P[3],Z);
  end;

begin
  Assert(CheckGLError,'Before GradientFill');

  glDisable(GLTextureStyle);

  if (Direction=gdRadial) or (Direction=gdFromCenter) then
  begin
    glBegin(GL_TRIANGLE_FAN);

    TeeNormal(0,0,-1);

    SetColor(Colors[0].Color);
    TeeVertex3D((P[0].X+P[1].X)*0.5,(P[1].Y+P[2].Y)*0.5,Z);
    SetColor(Colors[Colors.Count-1].Color);
    TeeVertex3D(P[0],Z);
    TeeVertex3D(P[1],Z);
    TeeVertex3D(P[2],Z);
    TeeVertex3D(P[3],Z);
    TeeVertex3D(P[0],Z);
  end
  else
  begin
    glBegin(GL_QUADS);

    TeeNormal(0,0,-1);

    Case Direction of
       gdTopBottom  : DoLinear(False,True);
       gdBottomTop  : DoLinear(True,True);
       gdLeftRight  : DoLinear(False,False);
       gdRightLeft  : DoLinear(True,False);
    gdFromBottomLeft,
      gdDiagonalDown: DoDiagonal(True);
       gdFromTopLeft,
       gdDiagonalUp : DoDiagonal(False);
    end;
  end;

  glEnd;

  Assert(CheckGLError,'GradientFill');
end;

Procedure TGLCanvas.RectangleY(const Left,Top,Right,Z0,Z1:Integer);
begin
  if Brush.Style<>bsClear then
  begin
    BeginBrush(GL_QUADS);

    TeeNormal(0,1,0);
    TeeVertex3D(Left, Top,Z1);
    TeeVertex3D(Right,Top,Z1);
    TeeVertex3D(Right,Top,Z0);
    TeeVertex3D(Left, Top,Z0);

    glEnd;

    EndBrushBitmap;
  end;

  if IsPenVisible then
  begin
    BeginPen(GL_LINE_LOOP);
      TeeVertex3D(Left, Top,Z0);
      TeeVertex3D(Right,Top,Z0);
      TeeVertex3D(Right,Top,Z1);
      TeeVertex3D(Left, Top,Z1);
    glEnd;
    EndPen;
  end;

  Assert(CheckGLError,'RectangleY');
end;

Procedure TGLCanvas.RectangleWithZ(Const Rect:TRect; Z:Integer);
begin
  With Rect do
  begin
    if IsPenVisible then
    begin
      BeginPen(GL_LINE_LOOP);
        TeeVertex3D(Left, Top,   Z);
        TeeVertex3D(Right,Top,   Z);
        TeeVertex3D(Right,Bottom,Z);
        TeeVertex3D(Left, Bottom,Z);
      glEnd;

      if (Pen.Width>3) and (Pen.JoinStyle<>jsMitter) then
      begin
        glPointSize(Pen.Width);
        BeginPen(GL_POINTS);
          TeeVertex3D(Left,Top,Z);
          TeeVertex3D(Right,Top,Z);
          TeeVertex3D(Right,Bottom,Z);
          TeeVertex3D(Left,Bottom,Z);
        glEnd;
        glPointSize(1);
      end;

      EndPen;
    end;

    if Gradient.Visible then
       InternalGradient(Rect,Z)
    else
    if Brush.Style<>bsClear then
    begin
      BeginBrush(GL_QUADS);

      TeeNormal(0,0,-1);
      glTexCoord2f(0,ITextureRepeatY);
      TeeVertex3D(Left, Top,   Z);
      glTexCoord2f(ITextureRepeatX,ITextureRepeatY);
      TeeVertex3D(Left, Bottom,Z);
      glTexCoord2f(ITextureRepeatX,0);
      TeeVertex3D(Right,Bottom,Z);
      glTexCoord2f(0,0);
      TeeVertex3D(Right,Top,   Z);

      glEnd;
      EndBrushBitmap;
    end;
  end;

  Assert(CheckGLError,'RectangleWithZ');
end;

function TGLCanvas.IsPenVisible:Boolean;
begin
  result:=Pen.Visible and (Pen.Style<>psClear);
end;

Procedure TGLCanvas.RectangleZ(const Left,Top,Bottom,Z0,Z1:Integer);
begin
  if IsPenVisible then
  begin
    BeginPen(GL_LINE_LOOP);
      TeeVertex3D(Left,Top,   Z0);
      TeeVertex3D(Left,Bottom,Z0);
      TeeVertex3D(Left,Bottom,Z1);
      TeeVertex3D(Left,Top,   Z1);
    glEnd;
    EndPen;
  end;

  if Brush.Style<>bsClear then
  begin
    BeginBrush(GL_QUADS);

    TeeNormal(1,0,0);
    glTexCoord2f(0,1);
    TeeVertex3D(Left, Top,   Z0);
    glTexCoord2f(1,1);
    TeeVertex3D(Left, Bottom,Z0);
    glTexCoord2f(1,0);
    TeeVertex3D(Left,Bottom,Z1);
    glTexCoord2f(0,0);
    TeeVertex3D(Left,Top,   Z1);

    glEnd;
    EndBrushBitmap;
  end;

  Assert(CheckGLError,'RectangleZ');
end;

procedure TGLCanvas.InternalRectangle(const Rect: TRect);
begin
  TeeNormal(0,0,-1);

  With Rect do
  begin
    glTexCoord2d(0,ITextureRepeatY);
    TeeVertex2D(Left, Top);
    glTexCoord2d(0,0);
    TeeVertex2D(Left, Bottom);
    glTexCoord2d(ITextureRepeatX,0);
    TeeVertex2D(Right,Bottom);
    glTexCoord2d(ITextureRepeatX,ITextureRepeatY);
    TeeVertex2D(Right,Top);
  end;

  glEnd;
end;

procedure TGLCanvas.InternalRectangle(const Rect: TRect; const Z:Single);
begin
  TeeNormal(0,0,-1);

  With Rect do
  begin
    glTexCoord3f(0,ITextureRepeatY,Z);
    TeeVertex3D(Left, Top, Z);
    glTexCoord3f(0,0,Z);
    TeeVertex3D(Left, Bottom, Z);
    glTexCoord3f(ITextureRepeatX,0,Z);
    TeeVertex3D(Right,Bottom, Z);
    glTexCoord3f(ITextureRepeatX,ITextureRepeatY,Z);
    TeeVertex3D(Right,Top, Z);
  end;

  glEnd;
end;

procedure TGLCanvas.FillRect(const Rect: TRect);
begin
  if Gradient.Visible then
     InternalGradient(Rect,GradientZ)
  else
  if Brush.Style<>bsClear then
  begin
    BeginBrush(GL_QUADS);
    InternalRectangle(Rect);
    EndBrushBitmap;
  end;

  Assert(CheckGLError,'FillRect '+IntToStr(ISavedError));
end;

procedure TGLCanvas.Ellipse(const X1, Y1, X2, Y2: Integer);
begin
  EllipseWithZ(X1,Y1,X2,Y2,0);
  Assert(CheckGLError,'Ellipse');
end;

procedure TGLCanvas.InternalEllipse(const R:TRect; const Z:Single);
const
  Slices=90;
  PiStep=2*Pi/Slices;

var t : Integer;
    tmpSin,
    tmpCos : {$IFDEF D15}Single{$ELSE}Extended{$ENDIF};
begin
  glPushMatrix;

  with R do
  begin
    glTranslatef((Left+Right)*0.5,-(Top+Bottom)*0.5,-Z);
    glScalef((Right-Left)*0.5,(Bottom-Top)*0.5,1);
  end;

  if IsPenVisible then
  begin
    BeginPen(GL_LINE_LOOP);

    for t:=0 to Slices+1 do
    begin
      SinCos(t*piStep,tmpSin,tmpCos);
      TeeVertex3D(tmpSin,tmpCos,0);
    end;

    glEnd;
    EndPen;
  end;

  if Brush.Style<>bsClear then
  begin
    BeginBrush(GL_TRIANGLE_FAN);

    TeeNormal(0,0,-1);
    TeeVertex3D(0,0,0);

    for t:=0 to Slices do
    begin
      SinCos(t*piStep,tmpSin,tmpCos);
      TeeVertex3D(tmpSin,tmpCos,0);
    end;

    glEnd;

    EndBrushBitmap;
  end;

  glPopMatrix;

  Assert(CheckGLError,'InternalEllipse');
end;

procedure TGLCanvas.EllipseWithZ(const X1, Y1, X2, Y2, Z: Integer);
begin
  InternalEllipse(TeeRect(X1,Y1,X2,Y2),Z);
end;

Procedure TGLCanvas.EnableRotation;
begin
  FDepth:=IOldDepth;

  glMatrixMode(GL_PROJECTION);
  glPopMatrix;

  glMatrixMode(GL_MODELVIEW);
  glPopMatrix;
  Assert(CheckGLError,'EnableRotation');

  if IWasDepthTest>0 then
     glEnable(GL_DEPTH_TEST);

  if IWasLighting>0 then
     glEnable(GL_LIGHTING);
end;

Procedure TGLCanvas.DisableRotation;
begin
  glMatrixMode(GL_PROJECTION);
  glPushMatrix;
  glLoadIdentity;

  IOldDepth:=FDepth;
  FDepth:=0;

  glOrtho(0,FWidth+1,0,FHeight+1,-1000.1,400);
  Assert(CheckGLError,'Orthogonal: '+IntToStr(ISavedError));

  glMatrixMode(GL_MODELVIEW);
  glPushMatrix;
  glLoadIdentity;

  IWasDepthTest:=glIsEnabled(GL_DEPTH_TEST);

  if IWasDepthTest>0 then
     glDisable(GL_DEPTH_TEST);

  IWasLighting:=glIsEnabled(GL_LIGHTING);

  if IWasLighting>0 then
     glDisable(GL_LIGHTING);

  RectCenter(Bounds,FXCenter,FYCenter);
  glTranslatef(0, FHeight, 0);
  Assert(CheckGLError,'DisableRotation');
end;

function TGLCanvas.GetPixel3D(X,Y,Z:Integer): TColor;
begin
  result:=clWhite;  // TODO
end;

procedure TGLCanvas.SetPixel3D(X,Y,Z:Integer; Value: TColor);
begin
  if IsPenVisible then
  begin
    glBegin(GL_POINTS);
    SetColor(Value);
    TeeVertex3D(X,Y,Z);
    glEnd;

    Assert(CheckGLError,'Pixel3D');
  end;
end;

procedure TGLCanvas.SetPixel(X, Y: Integer; Value: TColor);
begin
  if IsPenVisible then
  begin
    glBegin(GL_POINTS);
    SetColor(Value);
    TeeVertex2D(X,Y);
    glEnd;

    Assert(CheckGLError,'Pixel');
  end;
end;

procedure TGLCanvas.Arc(const Left, Top, Right, Bottom, StartX, StartY, EndX, EndY: Integer);
begin
  Assert(CheckGLError,'Arc');
//  gluPartialDisk
end;

Function TGLCanvas.BeginBlending(const R:TRect; Transparency:TTeeTransparency):TTeeBlend;
begin
  result:=inherited BeginBlending(R,Transparency);
  ActivateBlend(ITransp<1);
end;

procedure TGLCanvas.EndBlending(const Blend:TTeeBlend);
begin
  inherited;
  ActivateBlend(ITransp<1); // Keep blend mode active if ITransp < 1 (not fully opaque)
end;

procedure TGLCanvas.Donut( XCenter,YCenter,XRadius,YRadius:Integer;
                           Const StartAngle,EndAngle,HolePercent:Double);
begin
  Pie3D(XCenter,YCenter,XRadius,YRadius,0,0,StartAngle,EndAngle,True,True,Round(HolePercent));
  Assert(CheckGLError,'Donut');
end;

procedure TGLCanvas.Pie(const R:TRect; const StartAngle,EndAngle:Double);
var tmpStart : Single;
    tmpEnd   : Single;
    Step,
    XC,YC : Single;
    tmpSin,
    tmpCos : {$IFDEF D15}Single{$ELSE}Extended{$ENDIF};
    t  : Integer;
    XR,
    YR : Single;
    P  : Array of TPoint3DFloat;
begin
  tmpStart:=StartAngle;

  if tmpStart>EndAngle then
     tmpEnd:=360+EndAngle
  else
     tmpEnd:=EndAngle;

  Step:=(tmpEnd-tmpStart)/(NumCirclePoints-2);

  XC:=(R.Right+R.Left)*0.5;
  YC:=(R.Bottom+R.Top)*0.5;

  SetLength(P,NumCirclePoints);

  P[0].X:=XC;
  P[0].Y:=YC;
  P[0].Z:=FZ;

  XR:=(R.Right-R.Left)*0.5;
  YR:=(R.Bottom-R.Top)*0.5;

  for t:=1 to NumCirclePoints-1 do
  begin
    SinCos((tmpStart+((t-1)*Step))*TeePiStep,tmpSin,tmpCos);

    with P[t] do
    begin
      X:=XC+XR*tmpCos;
      Y:=YC-YR*tmpSin;
      Z:=FZ;
    end;
  end;

  Polygon3DFloat(P);

  P:=nil;

  Assert(CheckGLError,'Pie');
end;

procedure TGLCanvas.Pie(X1, Y1, X2, Y2, X3, Y3, X4, Y4: Integer);
var Theta,
    Theta2 : Single;
begin
  CalcPieAngles(X1,Y1,X2,Y2,X3,Y3,X4,Y4,Theta,Theta2);
  Pie(TeeRect(X1,Y1,X2,Y2),Theta,Theta2);
end;

procedure TGLCanvas.Pie3D( XCenter,YCenter,XRadius,YRadius,Z0,Z1:Integer;
                           Const StartAngle,EndAngle:Double;
                           DarkSides,DrawSides:Boolean;
                           DonutPercent:Integer=0;
                           Gradient:TCustomTeeGradient=nil;
                           BevelPercent:Integer=0;
                           EdgeStyle:TEdgeStyle=edNone;
                           Transparency:TTeeTransparency=0;
                           BevelBright:Integer=0;
                           BevelBorder:Boolean=True;
                           HideSides:Boolean=False);
var piStep     : Single;
    tmpSin     : {$IFDEF D15}Single{$ELSE}Extended{$ENDIF};
    tmpCos     : {$IFDEF D15}Single{$ELSE}Extended{$ENDIF};
    tmpXRadius : Single;
    tmpYRadius : Single;
    NumSliceParts : Integer;

  Procedure GetXY(t:Integer; out x,y:Single);
  begin
    SinCos(((Pi*0.5)+StartAngle)+(t*piStep),tmpSin,tmpCos);
    X:=XCenter+tmpXRadius*tmpSin;
    Y:=YCenter+tmpYRadius*tmpCos;
  end;

  Procedure GetCenter(const x,y:Single; out tmpX,tmpY:Single);
  begin
    tmpX:=XCenter+(DonutPercent*(X-XCenter)*0.01);
    tmpY:=YCenter+(DonutPercent*(Y-YCenter)*0.01);
  end;

  Procedure DrawPieSlice(z,ANormal:Integer; Backward:Boolean);

    Procedure DrawSlice(Triangles:Boolean);
    var t : Integer;
        tmpTeX,
        tmpTeY : Single;

      Procedure DrawSliceStep;
      var X,Y : Single;
          tmpX,tmpY : Single;
      begin
        GetXY(t,X,Y);

        if not Triangles then
        begin
          GetCenter(X,Y,tmpX,tmpY);

          glTexCoord3d(t*tmpTeX,0,t*tmpTeY);

          TeeVertex3D(tmpX,tmpY,z);
        end;

        glTexCoord3d(t*tmpTeX,0,t*tmpTeY);
        TeeVertex3D(X,Y,z);
      end;

    begin
      tmpTeX:=ITextureRepeatX/(NumSliceParts+1);
      tmpTeY:=ITextureRepeatY/(NumSliceParts+1);

      if Triangles then
      begin
        glTexCoord3d(ITextureRepeatX*0.5,0,ITextureRepeatY*0.5);
        TeeVertex3D(XCenter,YCenter,z);
      end;

      if Backward then
         for t:=NumSliceParts downto 0 do DrawSliceStep
      else
         for t:=0 to NumSliceParts do DrawSliceStep;
    end;

  var t : Integer;
      X,Y : Single;
      tmpX,tmpY : Single;
  begin
    if IsPenVisible then
    begin
      BeginPen(GL_LINE_LOOP);

      if DonutPercent=0 then
      begin
        TeeVertex3D(XCenter,YCenter,z);

        for t:=0 to NumSliceParts do
        begin
          GetXY(t,X,Y);
          TeeVertex3D(X,Y,z);
        end;
      end
      else
      begin
        GetXY(0,X,Y);
        GetCenter(X,Y,tmpX,tmpY);
        TeeVertex3D(tmpX,tmpY,z);

        for t:=0 to NumSliceParts do
        begin
          GetXY(t,X,Y);
          TeeVertex3D(X,Y,z);
        end;

        for t:=NumSliceParts downto 0 do
        begin
          GetXY(t,X,Y);
          GetCenter(X,Y,tmpX,tmpY);
          TeeVertex3D(tmpX,tmpY,z);
        end;
      end;

      glEnd;
      EndPen;
    end;

    if Brush.Style<>bsClear then
    begin
      if DonutPercent=0 then
         BeginBrush(GL_TRIANGLE_FAN)
      else
         BeginBrush(GL_QUAD_STRIP);

      TeeNormal(0,ANormal,0);
      DrawSlice(DonutPercent=0);

      glEnd;
      EndBrushBitmap;
    end;

    tmpXRadius:=XRadius;
    tmpYRadius:=YRadius;
  end;

  Procedure DrawCover(Backward:Boolean);

    procedure DoLine3D(N:Integer);
    var A, B: TPoint3DFloat;
    begin
      GetXY(N,A.X,A.Z);
      A.Y:=Z0;
      Line(A,B);
      B.X:=A.x;
      B.y:=Z1;
      B.Z:=A.Z;
      Line(A,B);
    end;

  var t : Integer;
      tmpTex,
      x,y : Single;
  begin
    BeginBrush(GL_QUAD_STRIP);

    TeeNormal(0,1,0);

    tmpTex:=ITextureRepeatX/(NumSliceParts+1);

    if Backward then
    for t:=NumSliceParts downto 0 do
    begin
      GetXY(t,X,Y);

      glTexCoord2d(t*tmpTex,0);
      TeeVertex3D(X,Y,Z1);

      glTexCoord2d(t*tmpTex,ITextureRepeatY);
      TeeVertex3D(X,Y,Z0);
    end
    else
    for t:=0 to NumSliceParts do
    begin
      GetXY(t,X,Y);

      glTexCoord2d(t*tmpTex,0);
      TeeVertex3D(X,Y,Z1);

      glTexCoord2d(t*tmpTex,ITextureRepeatY);
      TeeVertex3D(X,Y,Z0);
    end;

    glEnd;

    EndBrushBitmap;
  end;

  // D20 issue, needs this function to avoid using System.Math.Vectors
  function Point3DFloat(const X,Y,Z:Single):TPoint3DFloat; {$IFDEF D9}inline;{$ENDIF}
  begin
    result.X:=X;
    result.Y:=Y;
    result.Z:=Z;
  end;

  Procedure DrawSide(Start:Boolean);
  var tmpX,
      tmpY,
      x,y : Single;
  begin
    if Start then
       GetXY(0,X,Y)
    else
       GetXY(NumSliceParts,X,Y);

    tmpX:=XCenter;
    tmpY:=YCenter;

    if DonutPercent<>0 then
       GetCenter(X,Y,tmpX,tmpY);

    Polygon3DFloat([Point3DFloat(X,Y,Z0),Point3DFloat(X,Y,Z1),Point3DFloat(tmpX,tmpY,Z1),Point3DFloat(tmpX,tmpY,Z0)]);
  end;

begin
  NumSliceParts:=Max(16,Round((EndAngle-StartAngle)*HalfDivPi) div 2);

  piStep:=(EndAngle-StartAngle)/NumSliceParts;

  tmpXRadius:=XRadius;
  tmpYRadius:=YRadius;

  if DrawSides then
  begin
    DrawSide(True);
    DrawSide(False);
  end;

  glEnable(GL_CULL_FACE);

  DrawCover(True);

  DrawPieSlice(z1,-1,True);
  DrawPieSlice(z0,1,False);

  if DonutPercent>0 then
  begin
    tmpXRadius:=DonutPercent*XRadius*0.01;
    tmpYRadius:=DonutPercent*YRadius*0.01;
    DrawCover(False);
  end;

  glDisable(GL_CULL_FACE);

  Assert(CheckGLError,'Pie3D');
end;

procedure TGLCanvas.Polyline(const Points:Array of TPoint3D);
var Count : Integer;
    t     : Integer;
begin
  Count:=Length(Points);

  if Count>0 then
  begin
    BeginPen(GL_LINE_STRIP);

    for t:=0 to Count-1 do
        TeeVertex3D(Points[t]);

    glEnd;
    EndPen;
    
    Assert(CheckGLError,'Polyline');

    with Points[Count-1] do
    begin
      FX:=X;
      FY:=Y;
      FZ:=Z;
    end;
  end;
end;

procedure TGLCanvas.Polyline(const Points:Array of TPoint; Z:Integer);
var Count : Integer;
    t     : Integer;
begin
  Count:=Length(Points);

  if Count>0 then
  begin
    BeginPen(GL_LINE_STRIP);

    for t:=0 to Count-1 do
        TeeVertex3D(Points[t],Z);

    glEnd;
    EndPen;

    Assert(CheckGLError,'Polyline');

    with Points[Count-1] do
    begin
      FX:=X;
      FY:=Y;
      FZ:=Z;
    end;
  end;
end;

{$DEFINE TEEVERTEXARRAY}

procedure TGLCanvas.Polyline({$IFDEF D6}const{$ENDIF} Points:Array of TPoint);
var Count : Integer;
begin
  {$IFDEF TEEVERTEXARRAY}
  Count:=Length(Points);

  if Count>0 then
  begin

    // Some settings that might increase speed here, but should already been disabled,
    // like:
    // AmbientLight:=0 and Light0,1,2 Visible:=False : to disable GL_LIGHTING
    // View3D:=False and Orthogonal:=False to disable GL_DEPTH_TEST
    //
    // glDisable: GL_normalize, GL_AutoNormal, stencil clip, GL_Dither


    SetColor(Pen.Color);
    glEnableClientState(GL_VERTEX_ARRAY);
//    glPushMatrix;
    glScalef(1,-1,1);
    glVertexPointer(2, GL_INT, 0, @Points[0]);
    glDrawArrays(GL_LINE_STRIP, 0, Count);
    glDisableClientState(GL_VERTEX_ARRAY);
    glScalef(1,-1,1);
//    glPopMatrix;

    Assert(CheckGLError,'Polyline');

    with Points[Count-1] do
    begin
      FX:=X;
      FY:=Y;
      FZ:=0;
    end;
  end;

  {$ELSE}
  Polyline(Points,0);
  {$ENDIF}
end;

procedure TGLCanvas.PolylineFloat(const Points:Array of TPointFloat);
var Count : Integer;
begin
  {$IFDEF TEEVERTEXARRAY}
  Count:=Length(Points);

  if Count>0 then
  begin

    // Some settings that might increase speed here, but should already been disabled,
    // like:
    // AmbientLight:=0 and Light0,1,2 Visible:=False : to disable GL_LIGHTING
    // View3D:=False and Orthogonal:=False to disable GL_DEPTH_TEST
    //
    // glDisable: GL_normalize, GL_AutoNormal, stencil clip, GL_Dither


    SetColor(Pen.Color);
    glEnableClientState(GL_VERTEX_ARRAY);

    //    glPushMatrix;
    glScalef(1,-1,1);

    glVertexPointer(2, GL_FLOAT, 0, @Points[0]);
    glDrawArrays(GL_LINE_STRIP, 0, Count);
    glDisableClientState(GL_VERTEX_ARRAY);

    glScalef(1,-1,1);
    //    glPopMatrix;

    Assert(CheckGLError,'Polyline');

    with Points[Count-1] do
    begin
      FX:=Round(X);
      FY:=Round(Y);
      FZ:=0;
    end;
  end;

  {$ELSE}
  inherited;
  {$ENDIF}
end;

procedure TGLCanvas.PolylineFloat(const Points:Array of TPoint3DFloat);
var Count : Integer;
begin
  {$IFDEF TEEVERTEXARRAY}
  Count:=Length(Points);

  if Count>0 then
  begin
    // Some settings that might increase speed here, but should already been disabled,
    // like:
    // AmbientLight:=0 and Light0,1,2 Visible:=False : to disable GL_LIGHTING
    // View3D:=False and Orthogonal:=False to disable GL_DEPTH_TEST
    //
    // glDisable: GL_normalize, GL_AutoNormal, stencil clip, GL_Dither

    SetColor(Pen.Color);
    glEnableClientState(GL_VERTEX_ARRAY);

    //    glPushMatrix;
    glScalef(1,-1,1);

    glVertexPointer(3, GL_FLOAT, 0, @Points[0]);
    glDrawArrays(GL_LINE_STRIP, 0, Count);
    glDisableClientState(GL_VERTEX_ARRAY);

    glScalef(1,-1,1);
    //    glPopMatrix;

    Assert(CheckGLError,'Polyline');

    with Points[Count-1] do
    begin
      FX:=X;
      FY:=Y;
      FZ:=Z;
    end;
  end;

  {$ELSE}
  inherited;
  {$ENDIF}
end;

{$IFOPT C+}
procedure errorCallback(errorCode:GLenum); stdcall;
begin
  Raise Exception.Create(gluErrorString(errorCode));
end;
{$ENDIF}

procedure edgeFlagCallback(flag:GLboolean); stdcall;
begin
end;

type
  TVertex4f=Array[0..3] of Single;
  TFourVertex=Array[0..3] of GLDouble;

  TCombineVertex=packed record
     V:TAffineDblVector;
     RGB:packed Array[0..3] of GLDouble;
  end;

  PCombineVertex=^TCombineVertex;

var
  tmpCombineData:Array of TCombineVertex;

procedure combineCallback(coords:TVector3D;
                          var vertex_data:TFourVertex;
                          weights:TVertex4f;
                          out outData:PCombineVertex); stdcall;
var P : PCombineVertex;
    l : Integer;
begin
  l:=Length(tmpCombineData);
  SetLength(tmpCombineData,l+1);

  p:=@tmpCombineData[l];

  P.V[0]:=coords[0];
  P.V[1]:=coords[1];
  P.V[2]:=coords[2];

  (*
  // Interpolation not necessary, as we aren't passing vertex RGB color
  for t:=0 to 3 do
      P.RGB[t]:= weights[0] * vertex_data[0][t] +
                 weights[1] * vertex_data[1][t] +
                 weights[2] * vertex_data[2][t] +
                 weights[3] * vertex_data[3][t];
  *)

  outData:=p;
end;

procedure TGLCanvas.DoConcave({$IFDEF D6}const{$ENDIF} Points: Array of TAffineDblVector; const Invert:Boolean=False);
var t : Integer;
begin
  if not Assigned(ITess) then
     PrepareTessDirect;

  if Brush.Style<>bsClear then
  begin
    glEnable(GL_CULL_FACE);  //<-- We dont know if Points are clockwise
    SetColor(Brush.Color);

    if Invert then
       gluTessNormal(ITess, 0, 0, -1)
    else
       gluTessNormal(ITess, 0, 0, 1);

    gluTessProperty(ITess, GLU_TESS_BOUNDARY_ONLY, GL_FALSE);

    gluTessBeginPolygon(ITess,nil);
      gluTessBeginContour(ITess);

      if Invert then
      for t:=High(Points) downto 0 do
          gluTessVertex(ITess, Points[t], @Points[t])
      else
      for t:=0 to High(Points) do
          gluTessVertex(ITess, Points[t], @Points[t]);

      gluTessEndContour(ITess);
    gluTessEndPolygon(ITess);

    tmpCombineData:=nil;

    EndBrushBitmap;
    glDisable(GL_CULL_FACE);
  end;

  Assert(CheckGLError,'DoConcave');
end;

{$DEFINE TEETRIARRAYS} // Seems slower than glBegin

var IVertexData : TPointFloatArray;

procedure beginCallback(Mode: GLenum); stdcall;
begin
end;

procedure endCallback; stdcall;
begin
end;

procedure Vertex2DCallback(VertexData: PGLDouble); stdcall;
var L : Integer;
begin
  L:=Length(IVertexData);
  SetLength(IVertexData,L+1);
  IVertexData[L].X:=PAffineDblVector(VertexData)[0];
  IVertexData[L].Y:=PAffineDblVector(VertexData)[1];
end;

procedure TGLCanvas.PrepareTess;
begin
  ITess2D:=gluNewTess;

  if not Assigned(ITess2D) then
     Exit;

  gluTessCallback(ITess2D, GLU_TESS_BEGIN, @beginCallback);
  gluTessCallback(ITess2D, GLU_TESS_VERTEX, @vertex2DCallback);
  gluTessCallback(ITess2D, GLU_TESS_END, @endCallback);

  {$IFOPT C+}
  gluTessCallback(ITess2D, GLU_TESS_ERROR, @errorCallback);
  {$ENDIF}

  gluTessCallback(ITess2D, GLU_TESS_COMBINE, @combineCallback);

  gluTessCallback(ITess2D, GLU_TESS_EDGE_FLAG, @edgeFlagCallback); // Force GL_TRIANGLES

  // gluTessProperty(ITess2D, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO);
  // gluTessProperty(ITess2D, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD); <-- default
end;

procedure TGLCanvas.PrepareTessDirect;
begin
  ITess:=gluNewTess;

  if not Assigned(ITess) then
     Exit;

  gluTessCallback(ITess, GLU_TESS_BEGIN, @glBegin);
  gluTessCallback(ITess, GLU_TESS_VERTEX, @glVertex3dv); //vertexCallback);
  gluTessCallback(ITess, GLU_TESS_END, @glEnd);

  {$IFOPT C+}
  gluTessCallback(ITess, GLU_TESS_ERROR, @errorCallback);
  {$ENDIF}

  gluTessCallback(ITess, GLU_TESS_COMBINE, @combineCallback);

  gluTessCallback(ITess, GLU_TESS_EDGE_FLAG, @edgeFlagCallback); // Force GL_TRIANGLES

  // gluTessProperty(ITess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO);
  // gluTessProperty(ITess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD); <-- default
end;

function TGLCanvas.Tessellate({$IFDEF D6}const{$ENDIF} X,Y:Array of TChartValue):TPointFloatArray;
type
  TDblVectors=Array of TAffineDblVector;

var t,L : Integer;
    tmp : TDblVectors;
    P : PAffineDblVector;
begin
  if not Assigned(ITess2D) then
     PrepareTess;

  IVertexData:=nil;

  gluTessBeginPolygon(ITess2D,nil);
    gluTessBeginContour(ITess2D);

    L:=Length(X);
    SetLength(tmp,L);

    for t:=0 to L-1 do
    begin
      P:=@tmp[t];

      P[0]:=X[t];
      P[1]:=Y[t];
      P[2]:=0;
    end;

    for t:=0 to L-1 do
        gluTessVertex(ITess2D, tmp[t], @tmp[t]);

    gluTessEndContour(ITess2D);
  gluTessEndPolygon(ITess2D);

  tmpCombineData:=nil;

  result:=IVertexData;

  Assert(CheckGLError,'Tessellate');
end;

procedure TGLCanvas.Triangles({$IFDEF D6}const{$ENDIF} P:Array of TPoint3D;
                              CreateCached:Boolean;
                              var Cached:TTeeCachedObject);
var tmpL : Integer;
begin
  tmpL:=Length(P);

  if (tmpL>0) and (Brush.Style<>bsClear) then
  begin
    SetBrushBitmap;
    SetColor(Brush.Color);

    if Assigned(Cached) then
       glCallList(TGLCachedObject(Cached).Lists[0])
    else
    begin
      if CreateCached then
      begin
        Cached:=TGLCachedObject.Create;
        TGLCachedObject(Cached).AddList;
      end;

      glScalef(1,-1,-1);

      {$IFDEF TEETRIARRAYS}
      glEnableClientState(GL_VERTEX_ARRAY);
      glVertexPointer(3, GL_INT, 0, @P[0]);
      glDrawArrays(GL_TRIANGLES, 0, tmpL);
      glDisableClientState(GL_VERTEX_ARRAY);
      {$ELSE}

      glBegin(GL_TRIANGLES);
      TeeNormal(0,0,-1);  // Global Normal for all triangles

      for t:=0 to tmpL-1 do
          glVertex3iv(@P[t]);

      glEnd;
      {$ENDIF}

      glScalef(1,-1,-1);

      if CreateCached then
         glEndList;
    end;

    EndBrushBitmap;
  end;
end;

procedure TGLCanvas.PolygonConcave({$IFDEF D6}const{$ENDIF} Points: Array of TPoint);
begin
  PolygonConcave(Points,0);
end;

procedure TGLCanvas.PolygonConcave({$IFDEF D6}const{$ENDIF} Points: Array of TPoint; Z:Integer);
var t,
    tmpL : Integer;
    tmpData : Array of TAffineDblVector;
begin
  tmpL:=Length(Points);
  if tmpL=0 then
     Exit;

  if Brush.Style<>bsClear then
  begin
    SetLength(tmpData,tmpL);

    for t:=0 to tmpL-1 do
    begin
      tmpData[t,0]:=Points[t].X;
      tmpData[t,1]:=-Points[t].Y;
      tmpData[t,2]:=-Z;
    end;

    DoConcave(tmpData);

    tmpData:=nil;
  end;

  if IsPenVisible then
     Polyline(Points,Z);
end;

procedure TGLCanvas.Polygon({$IFDEF D6}const{$ENDIF} Points: array of TPoint);
begin
  PolygonWithZ(Points,0);
  Assert(CheckGLError,'Polygon');
end;

procedure TGLCanvas.PlaneFour3D(Var Points:TFourPoints; Z0,Z1:Integer); 
var tmpNormal:GLMat;

  Procedure CalcNormalPlaneFour;
  var Qx,Qy,Qz,Px,Py,Pz:Single;
  begin
    Qx:= Points[3].x-Points[2].x;
    Qy:= Points[3].y-Points[2].y;
    Qz:= z1-z1;
    Px:= Points[0].x-Points[2].x;
    Py:= Points[0].y-Points[2].y;
    Pz:= z0-z1;
    tmpNormal[0]:= (Py*Qz - Pz*Qy);
    tmpNormal[1]:= (Pz*Qx - Px*Qz);
    tmpNormal[2]:= -(Px*Qy - Py*Qx);
  end;

  Procedure AddPoints;
  begin
    With Points[0] do TeeVertex3D(x,y,z0);
    With Points[1] do TeeVertex3D(x,y,z0);
    With Points[2] do TeeVertex3D(x,y,z1);
    With Points[3] do TeeVertex3D(x,y,z1);
  end;

begin
  if IsPenVisible then
  begin
    BeginPen(GL_LINE_LOOP);
    AddPoints;
    glEnd;
    EndPen;
  end;

  if Brush.Style<>bsClear then
  begin
    BeginBrush(GL_QUADS);

    CalcNormalPlaneFour;
    glNormal3fv(PGLFloat(@tmpNormal));
    AddPoints;
    glEnd;

    EndBrushBitmap;
  end;

  Assert(CheckGLError,'PlaneFour3D');
end;

// Convex and Simple polygons only.
// For non-convex see ConcavePolygon
procedure TGLCanvas.Polygon3D(const Points: array of TPoint3D);

  Procedure AddPoints;
  var t : Integer;
  begin
    for t:=Low(Points) to High(Points) do
    With Points[t] do
    begin
      TeeNormal(x,y,z);
      glTexCoord3i(x,y,z);
      TeeVertex3D(x,y,z);
    end;
  end;

begin
  if Brush.Style<>bsClear then
  begin
    BeginBrush(GL_POLYGON);
    AddPoints;
    glEnd;
    EndBrushBitmap;
  end;

  if IsPenVisible then
  begin
    BeginPen(GL_LINE_LOOP);
    AddPoints;
    glEnd;
    EndPen;
  end;

  Assert(CheckGLError,'Polygon3D');
end;

procedure TGLCanvas.CalcMinMax(const Points: array of TPoint3DFloat;
                               var AMin,AMax : TPoint3DFloat);
var t : Integer;
begin
  AMin:=Points[Low(Points)];
  AMax:=AMin;

  for t:=Low(Points)+1 to High(Points) do
  with Points[t] do
  begin
    if X<AMin.X then AMin.X:=X else
    if X>AMax.X then AMax.X:=X;

    if Y<AMin.Y then AMin.Y:=Y else
    if Y>AMax.Y then AMax.Y:=Y;

    if Z<AMin.Z then AMin.Z:=Z else
    if Z>AMax.Z then AMax.Z:=Z;
  end;
end;

// Convex and Simple polygons only.
// For non-convex see ConcavePolygon
procedure TGLCanvas.Polygon3DFloat(const Points: array of TPoint3DFloat);
var t : Integer;
    tmpHasTextures : Boolean;

    IRange : TPoint3DFloat;
    tmpMin : TPoint3DFloat;
    tmpMax : TPoint3DFloat;
begin
  if Length(Points)>0 then
  begin
    if Brush.Style<>bsClear then
    begin
      tmpHasTextures:=SetBrushBitmap;

      glBegin(GL_POLYGON);

      SetColor(Brush.Color);

      if tmpHasTextures then
      begin
        CalcMinMax(Points,tmpMin,tmpMax);

        IRange.X:=ITextureRepeatX/(tmpMax.X-tmpMin.X);
        IRange.Y:=ITextureRepeatY/(tmpMax.Y-tmpMin.Y);
        IRange.Z:=1/(tmpMax.Z-tmpMin.Z);
      end;

      for t:=Low(Points) to High(Points) do
      With Points[t] do
      begin
        glNormal3f(x,-y,-z);

        if tmpHasTextures then
           glTexCoord3d((x-tmpMin.X)*IRange.X,(y-tmpMin.Y)*IRange.Y,(z-tmpMin.Z)*IRange.Z);

        glVertex3f(x,-y,-z);
      end;

      glEnd;

      EndBrushBitmap;
    end;

    if IsPenVisible then
    begin
      BeginPen(GL_LINE_LOOP);

      for t:=Low(Points) to High(Points) do
      with Points[t] do
           glVertex3f(x,-y,-z);

      glEnd;
      EndPen;
    end;

    Assert(CheckGLError,'Polygon3D (Float)');
  end;
end;

procedure TGLCanvas.RoundRect(X1, Y1, X2, Y2, X3, Y3: Integer);
begin
  Rectangle(X1,Y1,X2,Y2);
  Assert(CheckGLError,'RoundRect');
end;

Procedure TGLCanvas.Repaint;
begin
  if Assigned(View3DOptions) then View3DOptions.Repaint;
end;

{$IFNDEF LINUX}
Procedure TGLCanvas.CreateFontOutlines(Index:Integer);
var tmp : Integer;

  procedure VectorFont(FontMode:Integer);
  begin
    if not wglUseFontOutlines(GetDCHandle,32,TeeFontListRange,FontCache[Index].Offset,
                       0,FontCache[Index].Extrusion,FontMode,nil) then
    begin
      tmp:=GetLastError;
      Assert(tmp=0,'UseFontOutlines: '+IntToStr(tmp));
    end;
  end;

begin
  case FontStyle of
    fsBitmap: begin
                (*
                // Dont know why, only accepts 224 (TeeFontListRange-1)
                if not wglUseFontBitmaps(GetDCHandle,32,TeeFontListRange-1,FontCache[Index].Offset) then
                begin
                  tmp:=GetLastError;
                  Assert(tmp=0,'UseFontBitmaps: '+IntToStr(tmp));
                end;
                *)
              end;

   fsOutline: VectorFont(WGL_FONT_LINES);
  else
    VectorFont(WGL_FONT_POLYGONS);
  end;
end;
{$ENDIF}

Function TGLCanvas.FontWeight:Integer;
begin
  if fsBold in Font.Style then
     result:=FW_BOLD
  else
     result:=FW_NORMAL;
end;

Procedure TGLCanvas.InitOpenGLFont;

  Function FontPitch:Cardinal;
  begin
    case Font.Pitch of
      fpVariable: result:=VARIABLE_PITCH;
      fpFixed: result:=FIXED_PITCH;
    else
      result:=DEFAULT_PITCH;
    end;
  end;

{$IFNDEF LINUX}
var Old,
    HFont    : THandle;
    tmpItalic,
    tmpUnderline,
    tmpStrike : Boolean;
{$ENDIF}
begin
  {$IFNDEF LINUX}
  tmpItalic:=fsItalic in Font.Style;
  tmpUnderline:=fsUnderline in Font.Style;
  tmpStrike:=fsStrikeOut in Font.Style;

  HFont := Windows.CreateFont(-MulDiv(Font.Size, Font.PixelsPerInch, 72),
                      0, 0, 0, FontWeight,
                      Cardinal(tmpItalic),
                      Cardinal(tmpUnderline),
                      Cardinal(tmpStrike),
                      Cardinal(Font.Charset),
                      OUT_TT_PRECIS, // OUT_DEFAULT_PRECIS,
                      CLIP_DEFAULT_PRECIS,
                      TeeFontAntiAlias, // DEFAULT_QUALITY {DRAFT_QUALITY},
                      FontPitch or FF_DONTCARE {FIXED_PITCH or FF_MODERN},

                      {$IFDEF D12}
                      PChar
                      {$ELSE}
                      PAnsiChar
                      {$ENDIF}
                      (Font.Name)
                      );

  Old:=SelectObject(GetDCHandle, HFont);

  with FontCache[INumFonts] do
  begin
    Name:=Font.Name;
    Weight:=FontWeight;
    Style:=Font.Style;
    Offset:=glGenLists(TeeFontListRange);
    Extrusion:=FontExtrusion;
    GLStyle:=FontStyle;
    Size:=Font.Size;
  end;

  CreateFontOutlines(INumFonts);
  DeleteObject(SelectObject(GetDCHandle,Old));
  {$ENDIF}
end;

Procedure TGLCanvas.TextOut3D(X,Y,Z:Integer; const Text:String; AllowHtml:Boolean);
begin
  TextOut3DFloat(X,Y,Z,Text);
end;

Function TGLCanvas.FindFont:Integer;
var t : Integer;
begin
  for t:=0 to INumFonts-1 do
  with FontCache[t] do
  if (Name=Font.Name) and
     (Weight=FontWeight) and
     (Style=Font.Style) and
     (Extrusion=FontExtrusion) and
     (GLStyle=FontStyle) and
     ((GLStyle<>fsBitmap) or (Size=Font.Size)) then
  begin
    result:=t;
    exit;
  end;

  if INumFonts<TeeMaxFonts then
  begin
    InitOpenGLFont;

    result:=INumFonts;

    Inc(INumFonts);
  end
  else result:=0;
end;

type
  TTeeBrushAccess=class(TTeeBrush);
  TTeeFontAccess=class(TTeeFont);

Procedure TGLCanvas.TextOut3DFloat(X,Y,Z:Single; const Text:String);

  function CreateTextureText(const tmp:TSize; const AShadow:TTeeShadow=nil):TBitmap;
  var tmpH : HFont;
      tmpCanvas: TTeeCanvas3D;
  begin
    result:=TBitmap.Create;

    TeeSetBitmapSize(result,tmp.cx,tmp.cy);

    if Assigned(AShadow) then
    begin
      result.Canvas.Font.Assign(ReferenceCanvas.Font);
      result.Canvas.Font.Color:=AShadow.Color;

      tmpH:=GetNewFont(result.Canvas.Handle,Font.Size);

      result.Canvas.TextOut(0,0,Text);

      if tmpH<>0 then
         DeleteObject(SelectObject(result.Canvas.Handle,tmpH));
    end
    else
    begin
      tmpCanvas:=TTeeCanvas3D.Create;
      tmpCanvas.ReferenceCanvas:=result.Canvas;
      tmpCanvas.AssignFont(Font);
      tmpCanvas.TextOut(0,0,Text);
      tmpCanvas.Free;
    end;

    result.TransparentColor:=clWhite;
    result.TransparentMode:=tmFixed;
    result.Transparent:=True;
  end;

var
  tmpLength : Integer;
  tmpText   : AnsiString;
  tmpBitmap : TBitmap;
  tmpBitmapTexture : GLuint;

  procedure DrawTextBitmap(AX,AY:Integer; const AZ:Single);
  begin
    SetBrushBitmap(tmpBitmap,tmpBitmapTexture);

    glDisable(GL_TEXTURE_GEN_S);
    glDisable(GL_TEXTURE_GEN_T);

    glBegin(GL_QUADS);
    //SetColor(clWhite);

    InternalRectangle(TeeRect(AX,AY,AX+tmpBitmap.Width,AY+tmpBitmap.Height),AZ);

    EndBrushBitmap(tmpBitmap);
  end;

var
  tmp : TSize;

  procedure DrawShadow(const AShadow:TTeeShadow; Factor:Integer);
  var OldTransp : Single;
      Nums      : Array[0..0] of GLUInt;
  begin
    if Assigned(AShadow) then
    with AShadow do
    if Visible and ((HorizSize<>0) or (VertSize<>0)) then
    begin
      OldTransp:=ITransp;
      ITransp:=(100-Transparency)*0.01;

      if FontStyle=fsBitmap then
      begin
        tmpBitmap:=CreateTextureText(tmp,AShadow);
        AddNewTexture(tmpBitmap,tmpBitmapTexture);

        DrawTextBitmap(Round( x+Factor*HorizSize),
                       Round( y+Factor*VertSize),
                       Z+1);

        tmpBitmap.Free;

        Nums[0]:=tmpBitmapTexture;
        glDeleteTextures(1,@Nums);
      end
      else
      begin
        glPushMatrix;
        glTranslatef(Factor*0.03*HorizSize,-Factor*0.03*VertSize,-0.1);

        SetColor(Color);

        glCallLists(tmpLength, GL_UNSIGNED_BYTE,
           {$IFDEF UNICODE}PAnsiChar{$ELSE}PChar{$ENDIF}(tmpText));

        glPopMatrix;
      end;

      ITransp:=OldTransp;
    end;
  end;

  procedure DrawFontLine(const YPos:Single);
  begin
    Pen.Style:=psSolid;
    Pen.Color:=Font.Color;

    FX:=X+(tmp.Cx*0.02);
    FY:=YPos;

    Pen.Width:=1;
    IOldPenWidth:=1;
    glLineWidth(tmp.Cy*0.1);

    LineTo(X+1.025*tmp.Cx,FY);

    IOldPenWidth:=0;
  end;

const
   tmpYFactor=0.7;

var tmpSize   : Single;
    tmpAlign  : Integer;
    tmpFont   : Integer;
    tmpPic    : Boolean;
    Nums      : Array[0..0] of GLUInt;
begin
  ReferenceCanvas.Font.Assign(Font);
  tmp:=ReferenceCanvas.TextExtent(Text);

  if FontStyle<>fsBitmap then
  begin
    tmpFont:=FindFont;

    // Preserve current "list base", to restore it later:
    // glPushAttrib(GL_LIST_BIT);

    {$IFOPT R+}
    {$DEFINE WASRANGE}
    {$ENDIF}

    {$R-}
    glListBase(FontCache[tmpFont].Offset-32);

    {$IFDEF WASRANGE}
    {$R+}
    {$UNDEF WASRANGE}
    {$ENDIF}

  end
  else
    tmpFont:=-1;

  tmpText:=AnsiString(Text); // Unicode WideString to Ansi String (RAD Studio 2010 and up)

  tmpLength:=Length(tmpText);

  tmpAlign:=TextAlign;
  if tmpAlign>=TA_BOTTOM then
     Dec(tmpAlign,TA_BOTTOM)
  else
  if FontStyle=fsBitmap then
     Y:=Y+(1.0*tmp.Cy)
  else
     Y:=Y+(tmpYFactor*tmp.Cy);

  if tmpAlign=TA_CENTER then
     X:=X-Round(0.55*tmp.Cx)
  else
  if tmpAlign=TA_RIGHT then
     X:=X-tmp.Cx {+(tmpLength*0.5)};

  glPushMatrix;

  if FontStyle<>fsBitmap then
     glTranslatef(x+1,-y-2,-z+2);

  if TeeTextAngleY<>0 then
     glRotatef(TeeTextAngleY,1,0,0);

  if TeeTextAngleZ<>0 then
     glRotatef(TeeTextAngleZ,0,1,0);

  if FontStyle<>fsBitmap then
  begin
    tmpSize:=Font.SizeFloat;

    glScalef(tmpSize*TeeTextWidthFactor,tmpSize*TeeTextHeightFactor,1);

    if FontStyle=fsOutline then
    begin
      SetPen(Font.OutLine);
      SetColor(Font.OutLine.Color);
    end;
  end;

  TeeNormal(0,0,-1);

  DrawShadow(TTeeFontAccess(Font).FShadow,1);
  DrawShadow(TTeeFontAccess(Font).FEmboss,-1);

  if FontStyle=fsBitmap then
  begin
    tmpBitmap:=CreateTextureText(tmp);
    AddNewTexture(tmpBitmap,tmpBitmapTexture);

    // glRasterPos2i(0,0);

    glTranslatef(x,-y,-z);
    glRotatef(180,0,1,0);
    glRotatef(180,0,0,1);
    glTranslatef(-x,y,z);

    DrawTextBitmap(Round(x),Round(y),Z);

    tmpBitmap.Free;

    Nums[0]:=tmpBitmapTexture;
    glDeleteTextures(1,@Nums);
  end
  else
  begin
    if FontStyle<>fsOutline then
       SetColor(Font.Color);

    // (Text as polygons do not provide texture coordinates)
    tmpPic:=TTeeBrushAccess(Font.Brush).HasImage and
            (Font.Picture.Graphic<>nil);

    if tmpPic then
    begin
      SetBrushBitmap(Font.Picture.Filtered);

      glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
      glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
      glEnable(GL_TEXTURE_GEN_S);
      glEnable(GL_TEXTURE_GEN_T);
    end;

    if FontCache[tmpFont].Extrusion>0 then
       glEnable(GL_CULL_FACE);

    glCallLists(tmpLength, GL_UNSIGNED_BYTE,
        {$IFDEF UNICODE}PAnsiChar{$ELSE}PChar{$ENDIF}(tmpText));

    glDisable(GL_CULL_FACE);

    if FontCache[tmpFont].Extrusion>0 then
    begin
      glFrontFace(GL_CCW);
      Assert(CheckGLError,'FrontFace');
    end;

    if tmpPic then
    begin
      EndBrushBitmap(Font.Picture.Filtered);

      glDisable(GL_TEXTURE_GEN_S);
      glDisable(GL_TEXTURE_GEN_T);
    end;

    if FontStyle=fsOutline then
       EndPen;
  end;

  glPopMatrix;

  if FontStyle=fsNormal then
  begin
    if fsUnderline in Font.Style then
       DrawFontLine(Y+0.2*tmp.Cy);

    if fsStrikeOut in Font.Style then
       DrawFontLine(Y-0.2*tmp.Cy);
  end;

  Assert(CheckGLError,'TextOut3D: '+IntToStr(ISavedError));
end;

Procedure TGLCanvas.TextOut(X,Y:Integer; const Text:String);
begin
  TextOut3D(x,y,0,Text);
end;

procedure TGLCanvas.MoveTo3D(const X,Y,Z:Integer);
begin
  FX:=X;
  FY:=Y;
  FZ:=Z;
end;

procedure TGLCanvas.MoveTo3D(const X,Y,Z:Single);
begin
  FX:=X;
  FY:=Y;
  FZ:=Z;
end;

procedure TGLCanvas.Line(const A,B:TPoint3DFloat);
begin
  BeginPen(GL_LINES);
    TeeVertex3D(A.X,A.Y,A.Z);
    TeeVertex3D(B.X,B.Y,B.Z);
  glEnd;
  EndPen;

  Assert(CheckGLError,'Line (Float)');
end;

procedure TGLCanvas.LineTo3D(const X,Y,Z:Integer);
begin
  BeginPen(GL_LINES);
    TeeVertex3D(FX,FY,FZ);
    TeeVertex3D(x,y,z);
  glEnd;
  EndPen;

  FX:=X;
  FY:=Y;
  FZ:=Z;

  Assert(CheckGLError,'LineTo3D');
end;

procedure TGLCanvas.LineTo3D(const X,Y,Z:Single);
begin
  BeginPen(GL_LINES);
    TeeVertex3D(FX,FY,FZ);
    TeeVertex3D(x,y,z);
  glEnd;
  EndPen;

  FX:=X;
  FY:=Y;
  FZ:=Z;

  Assert(CheckGLError,'LineTo3D (Float)');
end;

procedure TGLCanvas.PlaneWithZ(P1,P2,P3,P4:TPoint; Z:Integer);
begin
  if IsPenVisible then
  begin
    BeginPen(GL_LINE_LOOP);
      TeeVertex3D(P1.X,P1.Y,Z);
      TeeVertex3D(P2.X,P2.Y,Z);
      TeeVertex3D(P3.X,P3.Y,Z);
      TeeVertex3D(P4.X,P4.Y,Z);
    glEnd;
    EndPen;
  end;

  if Brush.Style<>bsClear then
  begin
    BeginBrush(GL_QUADS);

    TeeNormal(0,0,-1);
    TeeVertex3D(P1.X,P1.Y,Z);
    TeeVertex3D(P2.X,P2.Y,Z);
    TeeVertex3D(P3.X,P3.Y,Z);
    TeeVertex3D(P4.X,P4.Y,Z);
    glEnd;

    EndBrushBitmap;
  end;

  Assert(CheckGLError,'PlaneWithZ');
end;

procedure TGLCanvas.Plane3DFloat(Const A,B:TPointFloat; const Z0,Z1:Single);
begin
  if Brush.Style<>bsClear then
  begin
    BeginBrush(GL_QUADS);

    TeeNormal(0,1,0);  { <-- CalcNormal }
    TeeVertex3D(A.X,A.Y,Z0);
    TeeVertex3D(B.X,B.Y,Z0);
    TeeVertex3D(B.X,B.Y,Z1);
    TeeVertex3D(A.X,A.Y,Z1);
    glEnd;

    EndBrushBitmap;
  end;

  if IsPenVisible then
  begin
    BeginPen(GL_LINE_LOOP);
      TeeVertex3D(A.X,A.Y,Z0);
      TeeVertex3D(B.X,B.Y,Z0);
      TeeVertex3D(B.X,B.Y,Z1);
      TeeVertex3D(A.X,A.Y,Z1);
    glEnd;
    EndPen;
  end;

  Assert(CheckGLError,'Plane3D (float)');
end;

procedure TGLCanvas.Plane3D(Const A,B:TPoint; Z0,Z1:Integer);
begin
  if Brush.Style<>bsClear then
  begin
    BeginBrush(GL_QUADS);

    TeeNormal(0,1,0);  { <-- CalcNormal }
    TeeVertex3D(A.X,A.Y,Z0);
    TeeVertex3D(B.X,B.Y,Z0);
    TeeVertex3D(B.X,B.Y,Z1);
    TeeVertex3D(A.X,A.Y,Z1);
    glEnd;

    EndBrushBitmap;
  end;

  if IsPenVisible then
  begin
    BeginPen(GL_LINE_LOOP);
      TeeVertex3D(A.X,A.Y,Z0);
      TeeVertex3D(B.X,B.Y,Z0);
      TeeVertex3D(B.X,B.Y,Z1);
      TeeVertex3D(A.X,A.Y,Z1);
    glEnd;
    EndPen;
  end;

  Assert(CheckGLError,'Plane3D');
end;

procedure TGLCanvas.SetPolygonMode;
begin
  if FDrawStyle=tcsWire then
     glPolygonMode(TeeColorPlanes,GL_LINE)
  else
  if FDrawStyle=tcsDot then
     glPolygonMode(TeeColorPlanes,GL_POINT)
  else
     glPolygonMode(TeeColorPlanes,GL_FILL);

  Assert(CheckGLError,'SetPolygonMode: '+IntToStr(ISavedError));
end;

procedure TGLCanvas.SetDrawStyle(Value:TTeeCanvasSurfaceStyle);
begin
  FDrawStyle:=Value;

  if HRC<>0 then
     SetPolygonMode;
end;

Function TGLCanvas.GetSupports3DText:Boolean;
begin
  result:=True;
end;

Function TGLCanvas.GetSupportsFullRotation:Boolean;
begin
  result:=True;
end;

Function TGLCanvas.GetSupportsXORMode:Boolean;
begin
  result:=False;
end;

Function TGLCanvas.GetUseBuffer:Boolean;
begin
  result:=FUseBuffer;
end;

function TGLCanvas.CanvasStyle:TCanvasStyle;
begin
  result:=csOpenGL;
end;

function TGLCanvas.InternalUseBuffer:Boolean;
begin
  result:=(not IDrawToBitmap) and UseBuffer;
end;

Procedure TGLCanvas.SetUseBuffer(Value:Boolean);
begin
  if FUseBuffer<>Value then
  begin
    FUseBuffer:=Value;
    IDestCanvas:=nil;
    DestroyGLContext;
    Repaint;
  end;
end;

procedure TGLCanvas.SmoothShadow(const Shadow:TTeeShadow; const Rect:TRect;
                             Ellipse:Boolean; RoundSize:Integer;
                             const P:Array of TPoint;
                             DonutPercent:Integer=0;
                             const StartAngle:Double=0;
                             const EndAngle:Double=360);
var t : Integer;
    OldZ : Single;
begin
  Brush.Color:=Shadow.Color;
  Brush.Style:=bsSolid;
  Pen.Hide;

  if Length(P)>0 then
  begin
    glBegin(GL_POLYGON);
    SetColor(Brush.Color);
    TeeNormal(0,0,-1);

    for t:=Low(P) to High(P) do
    with P[t] do
         TeeVertex3D(x,y,Shadow.ZPosition);

    glEnd;
  end
  else
  if Ellipse then
  begin
    if EndAngle-StartAngle=360 then
       InternalEllipse(Rect,Shadow.ZPosition)
    else
    begin
      OldZ:=FZ;
      FZ:=Shadow.ZPosition;
      Pie(Rect,StartAngle,EndAngle);
      FZ:=OldZ;
    end;
  end
  else
  begin
    glBegin(GL_QUADS);
    SetColor(Brush.Color);

    TeeNormal(0,0,-1);

    with Rect do
    begin
      TeeVertex3D(Left, Top,   Shadow.ZPosition);
      TeeVertex3D(Left, Bottom,Shadow.ZPosition);
      TeeVertex3D(Right,Bottom,Shadow.ZPosition);
      TeeVertex3D(Right,Top,   Shadow.ZPosition);
    end;

    glEnd;
  end;

  Assert(CheckGLError,'SmoothShadow: '+IntToStr(ISavedError));
end;

Procedure TGLCanvas.DeleteFont;
var t : Integer;
begin
  for t:=0 to INumFonts-1 do
      DeleteFont(t);

  INumFonts:=0;
end;

Procedure TGLCanvas.DeleteFont(Index:Integer);
begin
  with FontCache[Index] do
  if Offset<>-1 then
  begin
    if glIsList(Offset)=GL_TRUE then
    begin
      glDeleteLists(Offset,TeeFontListRange);
      Assert(CheckGLError,'DeleteFont '+IntToStr(ISavedError));
    end;

    Offset:=-1;
    Name:='';
  end;
end;

Function TGLCanvas.GetHandle:TTeeCanvasHandle;
begin
  result:=FDC;
end;

procedure TGLCanvas.RotateLabel3D(x,y,z:Integer; Const St:String; RotDegree:Double;
                                  AllowHTML:Boolean=False);
begin
  glPushMatrix;
  glTranslatef(x,-y,0);
  glRotatef(RotDegree,0,0,1);
  glTranslatef(-x,y,-z);
  TextOut(X,Y,St);
  glPopMatrix;

  Assert(CheckGLError,'RotateLabel3D');
end;

procedure TGLCanvas.RotateLabel(x,y:Integer; Const St:String; RotDegree:Double;
                                AllowHTML:Boolean=False);
begin
  RotateLabel3D(x,y,0,St,RotDegree,AllowHTML);
end;

// Nothing to do here. OpenGL already clears it...
procedure TGLCanvas.EraseBackground(const Rect: TRect);
begin
  if Gradient.Visible then
     Gradient.Draw(Self,Rect);
end;

Procedure TGLCanvas.Arrow( Filled:Boolean;
                           Const FromPoint,ToPoint:TPoint;
                           ArrowWidth,ArrowHeight,Z0,Z1:Integer;
                           const ArrowPercent:Double);
Var x    : Single;
    y    : Single;
    SinA : Single;
    CosA : Single;

    Function CalcArrowPoint:TPoint;
    Begin
      result.X:=Round( x*CosA + y*SinA);
      result.Y:=Round(-x*SinA + y*CosA);
    end;

var tmpHoriz  : Integer;
    tmpVert   : Integer;
    tmpZ      : Integer;
    dx        : Integer;
    dy        : Integer;
    tmpHoriz4 : Single;
    xb        : Single;
    yb        : Single;
    l         : Single;
    OldPen    : Boolean;

   { These are the Arrows points coordinates }
    To3D,pc,pd,pe,pf,pg,ph:TPoint;

    (*           pd
                   |\
    ph           pf| \
      |------------   \ ToPoint
 From |------------   /
    pg           pe| /
                   |/
                 pc
    *)
begin
  Assert(CheckGLError,'BeforeArrow');
  dx := ToPoint.x-FromPoint.x;
  dy := FromPoint.y-ToPoint.y;

  l  := TeeDistance(dx,dy);

  if l>0 then  { if at least one pixel... }
  Begin
    tmpHoriz:=ArrowWidth;
    tmpVert :=Math.Min(Round(l),ArrowHeight);

    tmpZ:=(Z0+Z1) div 2;

    SinA:= dy / l;
    CosA:= dx / l;

    xb:= ToPoint.x*CosA - ToPoint.y*SinA;
    yb:= ToPoint.x*SinA + ToPoint.y*CosA;

    x := xb - tmpVert;
    y := yb - tmpHoriz/2;
    pc:=CalcArrowPoint;

    y := yb + tmpHoriz/2;
    pd:=CalcArrowPoint;

    if Filled then
    Begin
      tmpHoriz4:=tmpHoriz*(ArrowPercent*0.005);
      y := yb - tmpHoriz4;
      pe:=CalcArrowPoint;
      y := yb + tmpHoriz4;
      pf:=CalcArrowPoint;
      x := FromPoint.x*cosa - FromPoint.y*sina;
      y := yb - tmpHoriz4;
      pg:=CalcArrowPoint;
      y := yb + tmpHoriz4;
      ph:=CalcArrowPoint;
      To3D:=ToPoint;

      OldPen:=Pen.Visible;
      Pen.Hide;

      PolygonWithZ([ph,pg,pe,pf],tmpZ);
      PolygonWithZ([pc,To3D,pd],tmpZ);

      Pen.Visible:=OldPen;

      if IsPenVisible then
      begin
        Brush.Style:=bsClear;
        PolygonWithZ([ph,pg,pe,pc,To3D,pd,pf],tmpZ);
      end;
    end
    else
    begin
      MoveTo3D(FromPoint.x,FromPoint.y,tmpZ);
      LineTo3D(ToPoint.x,ToPoint.y,tmpZ);
      LineTo3D(pd.x,pd.y,tmpZ);
      MoveTo3D(ToPoint.x,ToPoint.y,tmpZ);
      LineTo3D(pc.x,pc.y,tmpZ);
    end;
  end;

  Assert(CheckGLError,'Arrow');
end;

Procedure TGLCanvas.LineWithZ(X0,Y0,X1,Y1,Z:Integer);
begin
  MoveTo3D(X0,Y0,Z);
  LineTo3D(X1,Y1,Z);
end;

type
  //TRGBA=record R,G,B,A:Byte; end;
  TVertex=record
    X : Double;
    Y : Double;
    Z : Double;
    //RGBA : TRGBA;
  end;

  PVertex=^TVertex;
  //TFourVertex=array[0..3] of TRGBA;

  TVertexCoord=record
    Point : TPoint3DFloat;
    Coord : TPoint3DFloat;
  end;
  PVertexCoord=^TVertexCoord;

//  TVertex4f=Array[0..3] of Single;

var
  tessData : array of TVertex;

procedure combineCallback2(coords:TVector3D;
                          var vertex_data:Pointer {TFourVertex};
                          weights:TVertex4f;
                          out outData:Pointer); stdcall;
var tmp : Integer;
begin
  tmp:=Length(tessData);
  SetLength(tessData,tmp+1);

  outData:=@tessData[tmp];

  PVertex(outData).X:=coords[0];
  PVertex(outData).Y:=coords[1];
  PVertex(outData).Z:=coords[2];
end;

procedure VertexCallback2(VertexData: PVertexCoord; UserData: Pointer); stdcall;
begin
  with VertexData^ do
  begin
    glTexCoord3fv(@Coord);
    glVertex3fv(@Point);
  end;
end;

procedure OldDoConcave(const ACanvas:TGLCanvas; const ATextureSize:TPoint; const P:TPoint3DArray; Invert:Boolean=False);
var tess : PGLUTesselator;
    tmpL,
    t    : Integer;
    tmpTexture : TPoint;
    PData : packed Array of TVertexCoord;
    IRange,
    tmpMin,
    tmpMax : TPoint3DFloat;

    tmpP : TVector3d;
begin
  tmpL:=Length(P);

  if tmpL=0 then
     Exit;

  tess:=gluNewTess;

  if Assigned(tess) then
  begin
    tessData:=nil;

    SetLength(PData,tmpL);

    if (ATextureSize.X<>0) or (ATextureSize.Y<>0) then
    begin
      ACanvas.{$IFDEF BLOCKS}iCalcMinMax{$ELSE}CalcMinMax{$ENDIF}(P,tmpMin,tmpMax);

      if tmpMax.X=tmpMin.X then
         IRange.X:=0
      else
         IRange.X:=1/(tmpMax.X-tmpMin.X);

      if tmpMax.Y=tmpMin.Y then
         IRange.Y:=0
      else
         IRange.Y:=1/(tmpMax.Y-tmpMin.Y);

      if tmpMax.Z=tmpMin.Z then
         IRange.Z:=0
      else
         IRange.Z:=1/(tmpMax.Z-tmpMin.Z);

      for t:=0 to tmpL-1 do
      with PData[t] do
      begin
        Point:=P[t];

        with Coord do
        begin
          X:=tmpTexture.X*(Point.X-tmpMin.X)*IRange.X;
          Y:=tmpTexture.Y*(Point.Y-tmpMin.Y)*IRange.Y;
          Z:=(Point.Z-tmpMin.Z)*IRange.Z;
        end;
      end;
    end
    else
      for t:=0 to tmpL-1 do
      with PData[t] do
      begin
        Point:=P[t];
        Coord.X:=0;
        Coord.Y:=0;
        Coord.Z:=0;
      end;

//    gluTessCallback(tess, GLU_TESS_VERTEX_DATA,  @VertexCallback2);
    gluTessCallback(tess, GLU_TESS_BEGIN,   @glBegin);
    gluTessCallback(tess, GLU_TESS_END,     @glEnd);

    {$IFOPT C+}
    gluTessCallback(tess, GLU_TESS_ERROR,   @errorCallback);
    {$ENDIF}

    gluTessCallback(tess, GLU_TESS_COMBINE, @combineCallback);

    {
    Problem: Normal can't be calculated properly for anticlockwise concave points,
    and this makes the polygon to hide (culling).

    if Invert then
       with CalculateNormal(P[tmpL-1],P[tmpL-2],P[tmpL-3]) do
            gluTessNormal(tess,X,Y,Z)
    else
       with CalculateNormal(P[0],P[1],P[2]) do
            gluTessNormal(tess,X,Y,Z);
    }
    {
    if Invert then
       gluTessNormal(tess, 0, 0, -1)
    else
       gluTessNormal(tess, 0, 0, 1);
    }

    gluTessBeginPolygon(tess,P);
     gluTessBeginContour(tess);

     if Invert then
     for t:=tmpL-2 downto 0 do
     begin
       //CDI the following seems to work just as well
       //do we really need PData?
       //gluTessVertex(tess, TAffineDblVector(P[t]), @TAffineDblVector(P[t])
       tmpP[0]:=P[t].X;
       tmpP[1]:=P[t].Y;
       tmpP[2]:=P[t].Z;

       gluTessVertex(tess, tmpP, @PData[t])
     end
     else
     for t:=0 to tmpL-2 do
     begin
       tmpP[0]:=P[t].X;
       tmpP[1]:=P[t].Y;
       tmpP[2]:=P[t].Z;

       gluTessVertex(tess, tmpP, @PData[t]);
     end;

     gluTessEndContour(tess);
    gluTessEndPolygon(tess);

    gluDeleteTess(tess);

    // GLU: Do not call here: Assert(CheckGLError...)

    tessData:=nil;

    PData:=nil;
  end
end;

procedure TGLCanvas.Polygon3DConcave({$IFDEF D6}const{$ENDIF} Points: Array of TPoint3DFloat; const Invert:Boolean=False);
var t : Integer;
    P : Array of TAffineDblVector;
begin
  if Length(Points)>0 then
  begin
    if Brush.Style<>bsClear then
    begin
      SetLength(P,Length(Points));

      for t:=0 to Length(Points)-1 do
      with Points[t] do
      begin
        P[t,0]:=X;
        P[t,1]:=Y;
        P[t,2]:=Z;
      end;

      DoConcave(P,Invert);

      P:=nil;
    end;

    if IsPenVisible then
       PolylineFloat(Points);
  end;
end;

procedure TGLCanvas.Polygon3DConcave({$IFDEF D6}const{$ENDIF} Points: Array of TPoint3D; const Invert:Boolean=False);
var t,
    tmpL : Integer;
    tmpData : Array of TAffineDblVector;
begin
  tmpL:=Length(Points);
  if tmpL=0 then
     Exit;

  if Brush.Style<>bsClear then
  begin
    SetLength(tmpData,tmpL-1);

    for t:=0 to tmpL-2 do
    begin
      tmpData[t,0]:=Points[t].X;
      tmpData[t,1]:=-Points[t].Y;
      tmpData[t,2]:=-Points[t].Z;
    end;

    DoConcave(tmpData,Invert);

    tmpData:=nil;
  end;

  if IsPenVisible then
     Polyline(Points);
end;

// Convex and Simple polygons only.
// For non-convex see Triangulation (Ear-Clipping method) ConcavePolygon above.
procedure TGLCanvas.PolygonWithZ(const Points: Array of TPoint; Z:Integer);

  Procedure AddPoints;
  var t : Integer;
  begin
    for t:=Low(Points) to High(Points) do
    with Points[t] do
      if (t=Low(Points)) or (X<>Points[t-1].X) or (Y<>Points[t-1].Y) then
         TeeVertex3D(x,y,z);
  end;

var tmp : Integer;
begin
  tmp:=Length(Points);

  if Brush.Style<>bsClear then
  begin
    if tmp=3 then
       BeginBrush(GL_TRIANGLE_FAN)
    else
       BeginBrush(GL_POLYGON);

    TeeNormal(0,0,-1);
    AddPoints;
    glEnd;

    Assert(CheckGLError,'PolygonWithZ Brush');

    EndBrushBitmap;
  end;

  if IsPenVisible then
  begin
    BeginPen(GL_LINE_LOOP);
    AddPoints;
    glEnd;
    EndPen;

    Assert(CheckGLError,'PolygonWithZ Pen');
  end;
end;

procedure TGLCanvas.Triangle3D( Const Points:TTrianglePoints3D;
                                Const Colors:TTriangleColors3D);
var t:Integer;
begin
  if Brush.Style<>bsClear then
  begin
    SetBrushBitmap;
    glBegin(GL_POLYGON);

    TeeNormal(0,0,-1);  { <-- calc Normal }
    SetColor(Colors[0]);
    With Points[0] do TeeVertex3D(x,y,z);
    SetColor(Colors[1]);
    With Points[1] do TeeVertex3D(x,y,z);
    SetColor(Colors[2]);
    With Points[2] do TeeVertex3D(x,y,z);
    glEnd;

    EndBrushBitmap;
  end;

  if IsPenVisible then
  begin
    BeginPen(GL_LINE_LOOP);

    for t:=0 to 2 do
    with Points[t] do
         TeeVertex3D(x,y,z);

    glEnd;
    EndPen;
  end;

  Assert(CheckGLError,'Triangle3D');
end;

procedure TGLCanvas.TriangleWithZ(Const P1,P2,P3:TPoint; Z:Integer);
begin
  PolygonWithZ([P1,P2,P3],Z);
end;

function Normalize(const Normal:TPoint3DFloat):TPoint3DFloat;
var d : Single;
begin
  d:=Sqrt(Sqr(result.X) + Sqr(result.Y) + Sqr(result.Z));

  if d=0 then
  begin
    result.X:=1;
    result.Y:=0;
    result.Z:=0;
  end
  else
  begin
    d:=1/d;

    result.X:=result.X*d;
    result.Y:=result.Y*d;
    result.Z:=result.Z*d;
  end;
end;

function CalculateNormal(const A,B,C:TPoint3DFloat):TPoint3DFloat; overload;
var AB,
    BC : TPoint3DFloat;
begin
  AB.X:= b.X - a.X;
  AB.Y:= b.Y - a.Y;
  AB.Z:= b.Z - a.Z;

  BC.X:= c.X - a.X;
  BC.Y:= c.Y - a.Y;
  BC.Z:= c.Z - a.Z;

  result.X:= (AB.Y * BC.Z) - (BC.Y * AB.Z);
  result.Y:= (AB.Z * BC.X) - (BC.Z * AB.X);
  result.Z:= (AB.X * BC.Y) - (BC.X * AB.Y);
end;

function CalculateNormal(const P:TTrianglePoints3D):TPoint3DFloat; overload;
var A,B : TPoint3DFloat;
begin
  A.X:=P[1].X-P[0].X;
  A.Y:=P[1].Y-P[0].Y;
  A.Z:=P[1].Z-P[0].Z;

  B.X:=P[2].X-P[0].X;
  B.Y:=P[2].Y-P[0].Y;
  B.Z:=P[2].Z-P[0].Z;

  result.X:=A.Y*B.Z-A.Z*B.Y;
  result.Y:=A.Z*B.X-A.X*B.Z;
  result.Z:=A.X*B.Y-A.Y*B.X;
end;

procedure TGLCanvas.Surface3D(Style:TTeeCanvasSurfaceStyle;
                              SameBrush,CreateCached,AddNormals:Boolean;
                              NumXValues,NumZValues:Integer;
                              const CalcPoints:TTeeCanvasCalcPoints;
                              var Cached:TTeeCachedObject);

var
  Vertexes : Array of Array of TSurfaceVertex;

  procedure CalcCells;
  var
    FaceNormals : Array of TPoint3DArray;

    function AverageNormal(const Ax,Ay:Integer):TPoint3DFloat;
    var tmp0,
        tmp1,
        tmp2,
        tmp3 : TPoint3DFloat;
    begin
      // SSE3 candidate
      tmp0:=FaceNormals[Ax,Ay];
      tmp1:=FaceNormals[Ax+1,Ay];
      tmp2:=FaceNormals[Ax,Ay+1];
      tmp3:=FaceNormals[Ax+1,Ay+1];

      with result do
      begin
        X:=(tmp0.X+tmp1.X+tmp2.X+tmp3.X)*0.25;
        Y:=(tmp0.Y+tmp1.Y+tmp2.Y+tmp3.Y)*0.25;
        Z:=(tmp0.Z+tmp1.Z+tmp2.Z+tmp3.Z)*0.25;
      end;
    end;

  var tmpX,
      tmpZ : Integer;
  begin
    for tmpX:=1 to NumXValues-1 do
        for tmpZ:=NumZValues-1 downto 0 do
            CalcPoints(tmpX+1,tmpZ+1,not SameBrush,Vertexes[tmpX,tmpZ]);

    if AddNormals then
    begin
      // Calculate per-face normals:
      SetLength(FaceNormals,NumXValues,NumZValues);

      for tmpX:=1 to NumXValues-1 do
          for tmpZ:=0 to NumZValues-1 do
          with Vertexes[tmpX,tmpZ] do
               FaceNormals[tmpX,tmpZ]:=CalculateNormal(P0,P1,Vertexes[tmpX-1,tmpZ].P1);

      // Calculate per-vertex normals, averaging per-face:
      for tmpX:=0 to NumXValues-3 do
          for tmpZ:=0 to NumZValues-2 do
          with Vertexes[tmpX,tmpZ] do
          begin
            N0:=AverageNormal(tmpX,tmpZ);
            N1:=AverageNormal(tmpX+1,tmpZ);
          end;

      FaceNormals:=nil;
    end;
  end;

  Procedure DrawCells;
  var tmpX,
      tmpZ : Integer;
  begin
    for tmpX:=1 to NumXValues-1 do
    begin
      glBegin(GL_QUAD_STRIP);

      if not AddNormals then
         TeeNormal(0,1,0);

      for tmpZ:=NumZValues-1 downto 0 do
      with Vertexes[tmpX,tmpZ] do
      if Flag then
      begin
        if AddNormals then
           glNormal3fv(@N0);

        if SameBrush then
        begin
          With P0 do TeeVertex3D(x,y,z);

          if AddNormals then
             glNormal3fv(@N1);

          With P1 do TeeVertex3D(x,y,z);
        end
        else
        begin
          if C0<>clNone then SetColor(C0);
          With P0 do TeeVertex3D(x,y,z);

          if AddNormals then
             glNormal3fv(@N1);

          if C1<>clNone then SetColor(C1);
          With P1 do TeeVertex3D(x,y,z);
        end;
      end
      else
      begin
        glEnd;
        glBegin(GL_QUAD_STRIP);

        if not AddNormals then
           TeeNormal(0,-1,0);
      end;

      glEnd;
    end;
  end;

var tmpCached   : TTeeCachedObject;
    OldStyle    : TTeeCanvasSurfaceStyle;
begin
  tmpCached:=nil;

  SetPen(Pen);

  if (Style=tcsSolid) or (not SameBrush) then
     SetColor(Brush.Color);

  OldStyle:=DrawStyle;

  if DrawStyle=tcsSolid then
     SetDrawStyle(Style);

  if Assigned(Cached) then
     glCallList(TGLCachedObject(Cached).Lists[0])
  else
  begin
    SetLength(Vertexes,NumXValues,NumZValues);
    CalcCells;

    if CreateCached then
    begin
      tmpCached:=TGLCachedObject.Create;
      TGLCachedObject(tmpCached).AddList;
    end;

    DrawCells;

    if CreateCached then
       glEndList;
  end;

  if IsPenVisible and (Style=tcsSolid) then
  begin
    IPenAlpha:=(TRGBA(Pen.Color).Alpha>0) and (not IActiveBlend);

    if IPenAlpha then
       ActivateBlend(True);

    glPolygonMode(TeeColorPlanes,GL_LINE);
    SetColor(Pen.Color);
    SameBrush:=True;

    if Assigned(Cached) then
       glCallList(TGLCachedObject(Cached).Lists[1])
    else
    begin
      if CreateCached then
      begin
        if not Assigned(tmpCached) then
           tmpCached:=TGLCachedObject.Create;

        TGLCachedObject(tmpCached).AddList;
      end;

      DrawCells;

      if CreateCached then
         glEndList;
    end;

    if IPenAlpha then
       ActivateBlend(False);

    SetPolygonMode;
  end;

  Vertexes:=nil;

  SetDrawStyle(OldStyle);
  Assert(CheckGLError,'Surface3D');

  if CreateCached and (not Assigned(Cached)) then
     Cached:=tmpCached;
end;

 // We dont know if a given triangle is Clockwise or Counterclockwise,
 // therefore we cannot calculate per-triangle normals.
 // Also we dont know which triangles are sharing the same vertex, so its not
 // possible to average per-triangle normals to obtain per-vertex normals.


procedure TGLCanvas.TriSurface(NumTriangles:Integer; CreateCached,AddNormals:Boolean;
                            const Blend:TTeeBlend; Transparency:TTeeTransparency;
                            const CalcPoints:TTeeCanvasTriPoints;
                            var Cached:TTeeCachedObject);
var t,n : Integer;
    tmpColors : Array of TTriangleColors3D;
    tmpPoints : Array of TTrianglePoints3D;
    tmpNormals : TPoint3DArray;
    tmpIndexes: TTriangleIndexes;
begin
  if Assigned(Cached) then
  begin
    for t:=0 to Length(TGLCachedObject(Cached).Lists)-1 do
        glCallList(TGLCachedObject(Cached).Lists[t]);
  end
  else
  begin
    if CreateCached then
    begin
      Cached:=TGLCachedObject.Create;
      TGLCachedObject(Cached).AddList;
    end;

    SetLength(tmpPoints,NumTriangles);
    SetLength(tmpColors,NumTriangles);

    if AddNormals then
       SetLength(tmpNormals,NumTriangles*3);

    try
      for t:=0 to NumTriangles-1 do
          CalcPoints(t+1,tmpPoints[t],tmpColors[t],tmpIndexes);

      if AddNormals then
      begin
        n:=0;

        for t:=0 to NumTriangles-1 do
        begin
          tmpNormals[n]:=CalculateNormal(tmpPoints[t]);

          {
          tmpNormals[n].X:=Abs(tmpNormals[n].X);
          tmpNormals[n].Y:=Abs(tmpNormals[n].Y);
          tmpNormals[n].Z:=Abs(tmpNormals[n].Z);
          }

          tmpNormals[n+1]:=tmpNormals[n];
          tmpNormals[n+2]:=tmpNormals[n];

          Inc(n,3);
        end;
      end;

      if Brush.Style<>bsClear then
      begin
        glScalef(1,-1,-1);


        SetBrushBitmap;

        {$IFDEF TEETRIARRAYS}
        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_COLOR_ARRAY);

        if AddNormals then
        begin
          glEnableClientState(GL_NORMAL_ARRAY);
          glNormalPointer(GL_FLOAT, 0, @tmpNormals[0]);
        end;

        glVertexPointer(3, GL_INT, 0, @tmpPoints[0]);
        glColorPointer(4, GL_UNSIGNED_BYTE, 0, @tmpColors[0]);

        glDrawArrays(GL_TRIANGLES, 0, NumTriangles*3);

        if AddNormals then
           glDisableClientState(GL_NORMAL_ARRAY);

        glDisableClientState(GL_VERTEX_ARRAY);
        glDisableClientState(GL_COLOR_ARRAY);

        {$ELSE}

        glBegin(GL_TRIANGLES);

        if AddNormals then
        begin
          // Per-Triangle Normals

          n:=0;

          for t:=0 to NumTriangles-1 do
          begin
            SetColor(tmpColors[t,0]);
            glNormal3fv(@tmpNormals[n]);
            glVertex3iv(@tmpPoints[t,0]);

            SetColor(tmpColors[t,1]);
            glNormal3fv(@tmpNormals[n+1]);
            glVertex3iv(@tmpPoints[t,1]);

            SetColor(tmpColors[t,2]);
            glNormal3fv(@tmpNormals[n+2]);
            glVertex3iv(@tmpPoints[t,2]);

            Inc(n,3);
          end;
        end
        else
        begin
          TeeNormal(0,0,-1);  // Global Normal for all triangles

          for t:=0 to NumTriangles-1 do
          begin
            SetColor(tmpColors[t,0]);
            glVertex3iv(@tmpPoints[t,0]);

            SetColor(tmpColors[t,1]);
            glVertex3iv(@tmpPoints[t,1]);

            SetColor(tmpColors[t,2]);
            glVertex3iv(@tmpPoints[t,2]);
          end;
        end;

        glEnd;
        {$ENDIF}

        Assert(CheckGLError,'FillTriangles');

        EndBrushBitmap;


        glScalef(1,-1,-1);

        if CreateCached then
           glEndList;
      end;

      if IsPenVisible then
      begin
        if CreateCached then
           TGLCachedObject(Cached).AddList;

        glScalef(1,-1,-1);

        for t:=0 to NumTriangles-1 do
        begin
          BeginPen(GL_LINE_LOOP);

          glVertex3iv(@tmpPoints[t,0]);
          glVertex3iv(@tmpPoints[t,1]);
          glVertex3iv(@tmpPoints[t,2]);

          glEnd;
          EndPen;
        end;

        glScalef(1,-1,-1);

        if CreateCached then
           glEndList;
      end;

    finally
      tmpPoints:=nil;
      tmpColors:=nil;

      if AddNormals then
         tmpNormals:=nil;
    end;
  end;

  Assert(CheckGLError,'TriSurface: '+IntToStr(ISavedError));
end;

procedure TGLCanvas.PyramidTrunc(Const R: TRectF; const StartZ, EndZ, TruncX,TruncZ:Double;
                                 TopCover:Boolean=True; BottomCover:Boolean=True);
var widthDif,
    depthDif    : Single;
    p0,p1,p2,p3 : TPoint3DFloat;
Begin
  widthDif:=( R.Right-R.Left-(truncX*2) ) *0.5;
  depthDif:=( Abs(startZ-endZ)-(truncZ*2) ) *0.5;

  // Base
  p0.x:=r.Left;  p0.y:=r.Bottom; p0.z:=startZ;
  p1.x:=r.Left;  p1.y:=r.Bottom; p1.z:=endZ;
  p2.x:=r.Right; p2.y:=r.Bottom; p2.z:=endZ;
  p3.x:=r.Right; p3.y:=r.Bottom; p3.z:=startZ;
  Polygon3DFloat([p0,p1,p2,p3]);

  // Top
  p0.x:=r.Left+widthDif;  p0.y:=r.Top; p0.z:=endZ-depthDif;
  p1.x:=r.Right-widthDif; p1.y:=r.Top; p1.z:=endZ-depthDif;
  p2.x:=r.Right-widthDif; p2.y:=r.Top; p2.z:=startZ+depthDif;
  p3.x:=r.Left+widthDif;  p3.y:=r.Top; p3.z:=startZ+depthDif;
  Polygon3DFloat([p0,p1,p2,p3]);

  // Backwall
  p0.x:=r.Left;  p0.y:=r.Bottom; p0.z:=endZ;
  p1.x:=r.Right; p1.y:=r.Bottom; p1.z:=endZ;
  p2.x:=r.Right-widthDif; p2.y:=r.Top; p2.z:=endZ-depthDif;
  p3.x:=r.Left+widthDif;  p3.y:=r.Top; p3.z:=endZ-depthDif;
  Polygon3DFloat([p0,p1,p2,p3]);

  // Leftwall
  p0.x:=r.Left; p0.y:=r.Bottom; p0.z:=startZ;
  p1.x:=r.Left; p1.y:=r.Bottom; p1.z:=endZ;
  p2.x:=r.Left+widthDif; p2.y:=r.Top; p2.z:=endZ-depthDif;
  p3.x:=r.Left+widthDif; p3.y:=r.Top; p3.z:=startZ+depthDif;
  Polygon3DFloat([p0,p1,p2,p3]);

  // Rightwall
  p0.x:=r.Right; p0.y:=r.Bottom; p0.z:=startZ;
  p1.x:=r.Right; p1.y:=r.Bottom; p1.z:=endZ;
  p2.x:=r.Right-widthDif; p2.y:=r.Top; p2.z:=endZ-depthDif;
  p3.x:=r.Right-widthDif; p3.y:=r.Top; p3.z:=startZ+depthDif;
  Polygon3DFloat([p0,p1,p2,p3]);

  // Frontwall
  p0.x:=r.Left;  p0.y:=r.Bottom; p0.z:=startZ;
  p1.x:=r.Right; p1.y:=r.Bottom; p1.z:=startZ;
  p2.x:=r.Right-widthDif; p2.y:=r.Top; p2.z:=startZ+depthDif;
  p3.x:=r.Left+widthDif;  p3.y:=r.Top; p3.z:=startZ+depthDif;
  Polygon3DFloat([p0,p1,p2,p3]);
end;

initialization
  TeeRenderClasses.Add(TGLCanvas);
finalization
  TeeRenderClasses.Remove(TGLCanvas);
end.

