{*********************************************}
{  TeeChart Charting Library for FireMonkey   }
{  Copyright (c) 2012-2020 by Steema Software }
{  All Rights Reserved                        }
{*********************************************}
unit FMXTee.Editor.Chart3D;
{$I TeeDefs.inc}

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes,
  System.Variants, FMX.Types, FMX.Controls, FMX.Forms,
  FMX.Dialogs, FMX.Edit, FMX.Colors, FMX.ListBox, FMX.Layouts, FMX.TreeView,
  {$IFNDEF UPTOXE3}
  FMX.StdCtrls, FMX.Controls3D, FMX.MaterialSources, FMX.Viewport3D,
  FMX.Forms3D,
  {$ENDIF}
  {$IFDEF D19}
  FMX.Graphics,
  {$ENDIF}
  {$IFDEF D20}
  System.Math.Vectors, FMX.Text,
  {$ENDIF}
  FMX.Types3D, FMX.Objects3D, FMXTee.Editor.ThreeD.Lights,
  {$IFDEF D21}
  FMX.ComboTrackBar,
  {$ENDIF}
  FMX.TabControl, FMX.Menus;

type
  {$IFDEF D16}
  [ComponentPlatformsAttribute(pidWin32 or pidWin64 or pidOSX32 {$IFDEF D26}or pidOSX64{$ENDIF})]
  {$ENDIF}
  TObject3D=class(TControl3D)
  private
    FFileName: String;
    procedure SetFileName(const Value: String);
  protected
    procedure Render; override;
    procedure SetVisible(const Value: Boolean); override;
  public
    procedure Embedd(const AObject: TFmxObject);
    procedure LoadFromFile(const FileName:String);

    {$IFDEF D20}
    function RayCastIntersect(const RayPos, RayDir: TPoint3D; var Intersection: TPoint3D): Boolean; override;
    {$ELSE}
    function RayCastIntersect(const RayPos, RayDir: TVector3D; var Intersection: TVector3D): Boolean; override;
    {$ENDIF}
  published
    property FileName:String read FFileName write SetFileName;
  end;

  {$IFDEF D16}
  [ComponentPlatformsAttribute(pidWin32 or pidWin64 or pidOSX32 {$IFDEF D26}or pidOSX64{$ENDIF})]
  {$ENDIF}
  TAxis3D=class(TShape3D)
  private
    FFont: TFont;
    procedure SetFont(const Value: TFont);
  protected
    procedure FontChanged(Sender: TObject);
    procedure Render; override;
    procedure SetVisible(const Value: Boolean); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    {$IFDEF D20}
    function RayCastIntersect(const RayPos, RayDir: TPoint3D; var Intersection: TPoint3D): Boolean; override;
    {$ELSE}
    function RayCastIntersect(const RayPos, RayDir: TVector3D; var Intersection: TVector3D): Boolean; override;
    {$ENDIF}
  published
    property Font: TFont read FFont write SetFont;
  end;

  TMouse3D=class
  private
    FCamera  : TCamera;
    FControl : TControl3D;

    FOnMouseDown : TMouseEvent3D;
    FOnMouseUp : TMouseEvent3D;
    FOnMouseMove : TMouseMoveEvent3D;

    Down : Array[TMouseButton] of Boolean;
    OldAuto : Boolean;
    Old : TPointF;
    OldDown, OldUp : TMouseEvent3D;
    OldMove : TMouseMoveEvent3D;
    OldWheel : TMouseWheelEvent;

    ITarget : TControl3D;

    procedure MouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Single; RayPos, RayDir: TVector3D);
    procedure MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Single; RayPos, RayDir: TVector3D);
    procedure MouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Single; RayPos, RayDir: TVector3D);
    procedure MouseWheel(Sender: TObject; Shift: TShiftState;
      WheelDelta: Integer; var Handled: Boolean);
  public
    Constructor Create(const AControl:TControl3D; const ACamera:TCamera=nil);
    Destructor Destroy; override;

    property OnMouseDown:TMouseEvent3D read FOnMouseDown write FOnMouseDown;
    property OnMouseMove:TMouseMoveEvent3D read FOnMouseMove write FOnMouseMove;
    property OnMouseUp:TMouseEvent3D read FOnMouseUp write FOnMouseUp;
  end;

  TChart3DEditor = class(TForm)
    TreeView1: TTreeView;
    Splitter1: TSplitter;
    VertScrollBox1: TVertScrollBox;
    Expander1: TExpander;
    LClass: TLabel;
    CBClass: TComboBox;
    ListBoxItem1: TListBoxItem;
    ListBoxItem2: TListBoxItem;
    ListBoxItem3: TListBoxItem;
    ListBoxItem4: TListBoxItem;
    ListBoxItem5: TListBoxItem;
    ListBoxItem6: TListBoxItem;
    ListBoxItem7: TListBoxItem;
    Expander2: TExpander;
    CBDiffuse: TComboColorBox;
    CBBackDiffuse: TComboColorBox;
    Label1: TLabel;
    Label2: TLabel;
    CBOpacity: TComboTrackBar;
    Label6: TLabel;
    CBTwoSide: TCheckBox;
    Expander3: TExpander;
    CBPosX: TComboTrackBar;
    Label3: TLabel;
    CBPosY: TComboTrackBar;
    Label4: TLabel;
    CBPosZ: TComboTrackBar;
    Label5: TLabel;
    Expander4: TExpander;
    CBRotX: TComboTrackBar;
    CBRotY: TComboTrackBar;
    CBRotZ: TComboTrackBar;
    Expander5: TExpander;
    CBWidth: TComboTrackBar;
    CBHeight: TComboTrackBar;
    CBDepth: TComboTrackBar;
    Viewport3D1: TViewport3D;
    LName: TLabel;
    Light3: TLight;
    BBitmap: TButton;
    CBLighting: TCheckBox;
    CBViewPortColor: TComboColorBox;
    CBMultiSample: TComboBox;
    ListBoxItem8: TListBoxItem;
    ListBoxItem9: TListBoxItem;
    ListBoxItem10: TListBoxItem;
    TabItem1: TTabItem;
    TabLighting: TTabItem;
    Splitter2: TSplitter;
    BReset: TButton;
    CBWire: TCheckBox;
    MenuBar1: TMenuBar;
    MenuItem1: TMenuItem;
    View3DViews: TMenuItem;
    Panel3DViews: TPanel;
    ViewportTop: TViewport3D;
    Splitter3: TSplitter;
    Splitter4: TSplitter;
    Light1: TLight;
    ViewportFront: TViewport3D;
    Light2: TLight;
    ViewObjectEditor: TMenuItem;
    Camera1: TCamera;
    Camera2: TCamera;
    Label7: TLabel;
    Expander6: TExpander;
    Label8: TLabel;
    Label9: TLabel;
    Label10: TLabel;
    CBScaleX: TComboTrackBar;
    Label11: TLabel;
    CBScaleY: TComboTrackBar;
    Label12: TLabel;
    CBScaleZ: TComboTrackBar;
    ViewportSide: TViewport3D;
    Camera3: TCamera;
    Light4: TLight;
    MenuItem2: TMenuItem;
    Dummy1: TDummy;
    MenuAxes: TMenuItem;
    Label13: TLabel;
    Label14: TLabel;
    Label15: TLabel;
    Expander7: TExpander;
    CBCursor: TComboBox;
    ProxyTop: TProxyObject;
    ProxyFront: TProxyObject;
    ProxySide: TProxyObject;
    Splitter5: TSplitter;
    procedure CBClassChange(Sender: TObject);
    procedure CBRotYChange(Sender: TObject);
    procedure CBRotZChange(Sender: TObject);
    procedure CBRotXChange(Sender: TObject);
    procedure TreeView1Change(Sender: TObject);
    procedure TreeView1ChangeCheck(Sender: TObject);
    procedure CBDiffuseChange(Sender: TObject);
    procedure CBOpacityChange(Sender: TObject);
    procedure CBTwoSideChange(Sender: TObject);
    procedure CBWidthChange(Sender: TObject);
    procedure CBHeightChange(Sender: TObject);
    procedure CBDepthChange(Sender: TObject);
    procedure CBPosXChange(Sender: TObject);
    procedure CBPosYChange(Sender: TObject);
    procedure CBPosZChange(Sender: TObject);
    procedure CBBackDiffuseChange(Sender: TObject);
    procedure BBitmapClick(Sender: TObject);
    procedure CBLightingChange(Sender: TObject);
    procedure CBViewPortColorChange(Sender: TObject);
    procedure CBMultiSampleChange(Sender: TObject);
    procedure BResetClick(Sender: TObject);
    procedure CBWireChange(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure View3DViewsClick(Sender: TObject);
    procedure ViewportTopPaint(Sender: TObject; Canvas: TCanvas;
      const ARect: TRectF);
    procedure ViewObjectEditorClick(Sender: TObject);
    procedure CBScaleXChange(Sender: TObject);
    procedure CBScaleYChange(Sender: TObject);
    procedure CBScaleZChange(Sender: TObject);
    procedure ViewportTopMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Single);
    procedure ViewportFrontMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Single);
    procedure ViewportSideMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Single);
    procedure MenuItem2Click(Sender: TObject);
    procedure Dummy1Render(Sender: TObject; Context: TContext3D);
    procedure Viewport3D1MouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Single);
    procedure MenuAxesClick(Sender: TObject);
    procedure CBCursorChange(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Viewport3D1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Single);
    procedure Viewport3D1Paint(Sender: TObject; Canvas: TCanvas;
      const ARect: TRectF);
  private
    { Private declarations }
    Changing : Boolean;
    FControl : TControl3D;
    ILights  : TLightsEditor;
    CurrentViewport : TFmxObject;

    FMouse3D : TMouse3D;
    FAxes: TAxis3D;

    Start : TControl3D;

    IReady : Boolean;

    function Current:TControl3D;
    procedure DrawBounds(const Context:TContext3D; const AObject:TFmxObject;
                         HoveredOnly:Boolean=False);
    procedure FillTree(const Visual:TControl3D);
    procedure MouseUp3D(Sender: TObject; Button: TMouseButton;
                          Shift: TShiftState; X, Y: Single;
                          RayPos, RayDir: TVector3D);
    procedure RefreshLights;
    procedure RefreshViewport(const AViewport:TFmxObject);
    procedure RemoveOnMouseDown;
    procedure RepaintViews;
  public
    { Public declarations }
    class procedure Edit(const AOwner:TComponent; const AControl:TControl3D);
  end;

implementation

{$R *.fmx}

uses
  System.UIConsts, FMXTee.Editor.Picture, FMXTee.Constants, FMX.Layers3D,
  {$IFDEF D17}
  FMX.Materials,
  {$ENDIF}
  FMXTee.Editor.Stroke, FMXTee.Editor.FMXAnimations;

function TChart3DEditor.Current:TControl3D;
begin
  result:=(TreeView1.Selected.TagObject as TControl3D);
end;

procedure SetHitTest(AControl3D:TControl3D; AValue:Boolean);
var t : Integer;
begin
  AControl3D.HitTest:=AValue;

  for t := 0 to AControl3D.ChildrenCount-1 do
      if AControl3D.Children[t] is TLayout3D then
         SetHitTest(TControl3D(AControl3D.Children[t]),AValue);
end;

procedure TChart3DEditor.CBViewPortColorChange(Sender: TObject);
begin
  if CurrentViewport is TViewport3D then
     TViewport3D(CurrentViewport).Color:=CBViewPortColor.Color
  else
  if CurrentViewport is TCustomForm3D then
     TCustomForm3D(CurrentViewport).Color:=CBViewPortColor.Color
end;

procedure TChart3DEditor.RefreshLights;
begin
  if not Assigned(ILights) then
     ILights:=TLightsEditor(EmbeddForm(TLightsEditor.Create(Self),TabLighting));

  ILights.RefreshViewport(CurrentViewport);
end;

procedure TChart3DEditor.RefreshViewport(const AViewport: TFmxObject);

  procedure SetMultiSample(const AMulti:TMultisample);
  begin
    case AMulti of
      TMultisample.{$IFDEF D20}FourSamples{$ELSE}ms4Samples{$ENDIF}: CBMultiSample.ItemIndex:=0;
      TMultisample.{$IFDEF D20}TwoSamples{$ELSE}ms2Samples{$ENDIF}: CBMultiSample.ItemIndex:=1;
    else
      CBMultiSample.ItemIndex:=2;
    end;
  end;

begin
  CurrentViewport:=AViewport;
  RefreshLights;

  if CurrentViewport is TViewport3D then
  begin
    CBViewPortColor.Color:=TViewport3D(CurrentViewport).Color;
    SetMultiSample(TViewport3D(CurrentViewport).Multisample);
  end
  else
  if CurrentViewport is TCustomForm3D then
  begin
    CBViewPortColor.Color:=TCustomForm3D(CurrentViewport).Color;
    SetMultiSample(TCustomForm3D(CurrentViewport).Multisample);
  end;

  RepaintViews;
end;

type
  TViewportAccess=class(TViewport3D);

class procedure TChart3DEditor.Edit(const AOwner: TComponent; const AControl: TControl3D);

  procedure DeleteCloneLights(const Old:TFmxObject; const Dest:TViewport3D);
  var t : Integer;
  begin
    t:=0;
    while t<Dest.ChildrenCount do
    if Dest.Children[t] is TLight then
       Dest.Children[t].DisposeOf
    else
       Inc(t);

    for t := 0 to Old.ChildrenCount-1 do
    if Old.Children[t] is TLight then
    begin
      TLight(Old.Children[t]).Clone(Dest.Owner).Parent:=Dest;
    end;
  end;

var tmp : TChart3DEditor;
    Old : TFmxObject;
begin
  tmp:=TChart3DEditor.Create(AOwner);
  try
    tmp.FControl:=AControl;

    tmp.Start:=TControl3D.Create(nil);
    tmp.Start.Width:=AControl.Width;
    tmp.Start.Height:=AControl.Height;
    tmp.Start.Depth:=AControl.Depth;
    tmp.Start.Position.Point:=AControl.Position.Point;
    tmp.Start.RotationAngle.Point:=AControl.RotationAngle.Point;
    tmp.Start.Scale.Point:=AControl.Scale.Point;

    //AControl.HitTest:=False;
    //SetHitTest(AControl,False);

    if AControl.Parent is TViewPort3D then
       tmp.Viewport3D1.Color:=TViewPort3D(AControl.Parent).Color;

    Old:=AControl.Parent;
    AControl.Parent:=tmp.Viewport3D1;

    tmp.ProxyTop.SourceObject:=tmp.FControl;
    tmp.ProxyFront.SourceObject:=tmp.FControl;
    tmp.ProxySide.SourceObject:=tmp.FControl;

    tmp.FMouse3D:=TMouse3D.Create(AControl); //TViewportAccess(tmp.Viewport3D1).GetDesignCamera
    tmp.FAxes:=TAxis3D.Create(tmp);
    tmp.FAxes.Visible:=False;
    tmp.Viewport3D1.AddObject(tmp.FAxes);

    try
      if (Old is TCustomForm3D) or (Old is TViewport3D) then
         DeleteCloneLights(Old, tmp.Viewport3D1);

      tmp.RefreshViewport(tmp.Viewport3D1);

      tmp.FillTree(AControl);
      tmp.TreeView1.Selected:=tmp.TreeView1.ItemByIndex(0);

      tmp.ShowModal;
    finally
      tmp.FMouse3D.Free;
      AControl.Parent:=Old;
      tmp.RemoveOnMouseDown;

      if AControl.Parent is TViewPort3D then
         TViewPort3D(AControl.Parent).Repaint;

      //SetHitTest(AControl,True);
    end;
  finally
    tmp.Free;
  end;
end;

type
  TShape3DClass=class of TShape3D;
  {$IFDEF D17}
  TShape3DAccess=class(TShape3D);
  {$ENDIF}

function HasTexture(const S:TShape3D):Boolean;
{$IFDEF D17}

   function ValidTexture(const ATexture:TTexture):Boolean;
   begin
     result:=Assigned(ATexture) and (not ATexture.IsEmpty);
   end;

var m : TMaterial;
{$ENDIF}
begin
  {$IFDEF D17}
  m:=TMaterialSource.ValidMaterial(TShape3DAccess(S).MaterialSource);
  result:=Assigned(m) and
          ((m is TTextureMaterial) and ValidTexture(TTextureMaterial(m).Texture)) or
          ((m is TLightMaterial) and ValidTexture(TLightMaterial(m).Texture));
  {$ELSE}
  result:=not S.Material.Texture.IsEmpty;
  {$ENDIF}
end;

function ShapeTexture(S:TShape3D):{$IFDEF D17}TTexture{$ELSE}TBitmap{$ENDIF};
{$IFDEF D17}
var m : TMaterial;
{$ENDIF}
begin
  {$IFDEF D17}
  result:=nil;

  m:=TMaterialSource.ValidMaterial(TShape3DAccess(S).MaterialSource);

  if Assigned(m) then
     if m is TTextureMaterial then
        result:=TTextureMaterial(m).Texture
     else
     if m is TLightMaterial then
        result:=TLightMaterial(m).Texture;

  {$ELSE}
  result:=S.Material.Texture;
  {$ENDIF}
end;

procedure LoadTexture(S:TShape3D; const AFile:String);
{$IFDEF D17}

  procedure DoLoad(const ATexture:TTexture);
  var f : TFileStream;
  begin
    f:=TFileStream.Create(AFile,fmOpenRead);
    try
      ATexture.LoadFromStream(f);
    finally
      f.DisposeOf;
    end;
  end;

var m : TMaterial;
{$ENDIF}
begin
  {$IFDEF D17}
  m:=TMaterialSource.ValidMaterial(TShape3DAccess(S).MaterialSource);

  if Assigned(m) and (m is TTextureMaterial) then
  begin
    if not Assigned(TTextureMaterial(m).Texture) then
       TTextureMaterial(m).Texture:=TTexture.Create;

    DoLoad(TTextureMaterial(m).Texture);
    s.Repaint;
  end
  else
  if Assigned(m) and (m is TLightMaterial) then
  begin
    if not Assigned(TLightMaterial(m).Texture) then
       TLightMaterial(m).Texture:=TTexture.Create;

    DoLoad(TLightMaterial(m).Texture);
    s.Repaint;
  end;
  {$ELSE}
  S.Material.Texture.LoadFromFile(AFile);
  {$ENDIF}
end;

procedure TChart3DEditor.BBitmapClick(Sender: TObject);
var tmp : String;
begin
  if Current is TShape3D then
  begin
    if not HasTexture(TShape3D(Current)) then
    begin
      tmp:=TBackImageEditor.GetPictureFileName(Self);

      if tmp<>'' then
      begin
        if not FileExists(tmp) then
           raise Exception.Create('Image file does not exist: '+tmp);

        LoadTexture(TShape3D(Current),tmp);
      end;
    end
    else
    begin
      ShapeTexture(TShape3D(Current)).SetSize(0,0);
      TShape3D(Current).Repaint;
    end;

    if HasTexture(TShape3D(Current)) then
       BBitmap.Text:=StringReplace(TeeMsg_ClearImage,'&','',[])
    else
       BBitmap.Text:=StringReplace(TeeMsg_BrowseImage,'&','',[]);

  end;
end;

procedure TChart3DEditor.BResetClick(Sender: TObject);
begin
  Current.Position.Point:=Start.Position.Point;
  Current.RotationAngle.Point:=Start.RotationAngle.Point;
  Current.Width:=Start.Width;
  Current.Height:=Start.Height;
  Current.Depth:=Start.Depth;
  Current.Scale.Point:=Start.Scale.Point;

  TreeView1Change(Self);
end;

{$IFDEF D17}
type
  TExtrudedShape3DAccess=class(TExtrudedShape3D);
{$ENDIF}

procedure TChart3DEditor.CBBackDiffuseChange(Sender: TObject);
{$IFDEF D17}
var m : TMaterialSource;
{$ENDIF}
begin
  if Current is TExtrudedShape3D then
     {$IFDEF D17}
     begin
       m:=TExtrudedShape3DAccess(Current).MaterialBackSource;

       if Assigned(m) then
       begin
         if m is TLightMaterialSource then
            TLightMaterialSource(m).Diffuse:=CBBackDiffuse.Color
         else
         if m is TColorMaterialSource then
            TColorMaterialSource(m).Color:=CBBackDiffuse.Color;

         Current.Repaint;
       end;
     end;
     {$ELSE}
     TExtrudedShape3D(Current).MaterialBack.Diffuse:=CBBackDiffuse.Color;
     {$ENDIF}
end;

procedure TChart3DEditor.CBClassChange(Sender: TObject);
var tmp : TClass;
    tmpCurr,
    tmpShape : TShape3D;
    Old : TFmxObject;
    OldName : TComponentName;
begin
  if Changing then
     Exit;

  tmp:=GetClass('T'+CBClass.Selected.Text);

  if Assigned(tmp) then
  begin
    tmpCurr:=Current as TShape3D;

    tmpShape:=TShape3D(TShape3DClass(tmp).Create(tmpCurr.Owner));

    tmpShape.Position.Assign(tmpCurr.Position);
    tmpShape.RotationAngle.Assign(tmpCurr.RotationAngle);
    tmpShape.RotationCenter.Assign(tmpCurr.RotationCenter);
    tmpShape.Scale.Assign(tmpCurr.Scale);
    tmpShape.Visible:=tmpCurr.Visible;
    tmpShape.Cursor:=tmpCurr.Cursor;
    tmpShape.TwoSide:=tmpCurr.TwoSide;

    {$IFDEF D17}
    tmpShape.MaterialSource:=tmpCurr.MaterialSource;
    {$ELSE}
    tmpShape.Material:=tmpCurr.Material;
    {$ENDIF}

    Old:=tmpCurr.Parent;
    tmpCurr.Parent:=nil;
    tmpShape.Parent:=Old;

    OldName:=tmpCurr.Name;
    tmpCurr.Free;
    tmpShape.Name:=OldName;

    TreeView1.Selected.TagObject:=tmpShape;
    LClass.Text:=tmpShape.ClassName;
  end;
end;

procedure TChart3DEditor.CBCursorChange(Sender: TObject);
var tmp : Integer;
begin
  if not Changing then
     if IdentToCursor(TeeCursorPrefix+CBCursor.Selected.Text,tmp) then
        Current.Cursor:=tmp;
end;

procedure TChart3DEditor.CBDepthChange(Sender: TObject);
begin
  if IReady and (not Changing) then
     Current.Depth:=CBDepth.Value*0.01;
end;

procedure TChart3DEditor.CBDiffuseChange(Sender: TObject);
{$IFDEF D17}
var m : TMaterial;
{$ENDIF}
begin
  {$IFDEF D17}
  m:=TMaterialSource.ValidMaterial(TShape3DAccess(Current).MaterialSource);

  if Assigned(m) then
  begin
    if m is TLightMaterial then
       TLightMaterial(m).Diffuse:=CBDiffuse.Color
    else
    if m is TColorMaterial then
       TColorMaterial(m).Color:=CBDiffuse.Color;

    Current.Repaint;
  end;
  {$ELSE}
  TShape3D(Current).Material.Diffuse:=CBDiffuse.Color;
  {$ENDIF}
end;

procedure TChart3DEditor.CBHeightChange(Sender: TObject);
begin
  if IReady and (not Changing) then
     Current.Height:=CBHeight.Value*0.01;
end;

procedure TChart3DEditor.CBLightingChange(Sender: TObject);
begin
  if Current is TShape3D then
     {$IFNDEF D17}
     TShape3D(Current).Material.Lighting:=CBLighting.IsChecked;
     {$ENDIF}
end;

procedure TChart3DEditor.CBMultiSampleChange(Sender: TObject);

  function TheMultisample:TMultisample;
  begin
    case CBMultiSample.ItemIndex of
      0: result:=TMultiSample.{$IFDEF D20}FourSamples{$ELSE}ms4Samples{$ENDIF};
      1: result:=TMultiSample.{$IFDEF D20}TwoSamples{$ELSE}ms2Samples{$ENDIF};
    else
      result:=TMultiSample.{$IFDEF D20}None{$ELSE}msNone{$ENDIF};
    end;
  end;

begin
  if CurrentViewport is TViewport3D then
     TViewport3D(CurrentViewport).Multisample:=TheMultiSample
  else
  if CurrentViewport is TCustomForm3D then
     TCustomForm3D(CurrentViewport).Multisample:=TheMultiSample;

  Viewport3D1.Repaint;
end;

procedure TChart3DEditor.CBOpacityChange(Sender: TObject);
begin
  if IReady and (not Changing) then
     Current.Opacity:=CBOpacity.Value;
end;

procedure TChart3DEditor.CBPosXChange(Sender: TObject);
begin
  if IReady and (not Changing) then
     Current.Position.X:=CBPosX.Value*0.01;
end;

procedure TChart3DEditor.CBPosYChange(Sender: TObject);
begin
  if IReady and (not Changing) then
     Current.Position.Y:=CBPosY.Value*0.01;
end;

procedure TChart3DEditor.CBPosZChange(Sender: TObject);
begin
  if IReady and (not Changing) then
     Current.Position.Z:=CBPosZ.Value*0.01;
end;

procedure TChart3DEditor.CBRotXChange(Sender: TObject);
begin
  if IReady and (not Changing) then
     Current.RotationAngle.X:=CBRotX.Value;
end;

procedure TChart3DEditor.CBRotYChange(Sender: TObject);
begin
  if IReady and (not Changing) then
     Current.RotationAngle.Y:=CBRotY.Value;
end;

procedure TChart3DEditor.CBRotZChange(Sender: TObject);
begin
  if IReady and (not Changing) then
     Current.RotationAngle.Z:=CBRotZ.Value;
end;

procedure TChart3DEditor.CBScaleXChange(Sender: TObject);
begin
  if IReady and (not Changing) then
     if CBScaleX.Value<0 then
        Current.Scale.X:=-1/CBScaleX.Value
     else
        Current.Scale.X:=CBScaleX.Value;
end;

procedure TChart3DEditor.CBScaleYChange(Sender: TObject);
begin
  if IReady and (not Changing) then
     if CBScaleY.Value<0 then
        Current.Scale.Y:=-1/CBScaleY.Value
     else
        Current.Scale.Y:=CBScaleY.Value;
end;

procedure TChart3DEditor.CBScaleZChange(Sender: TObject);
begin
  if IReady and (not Changing) then
     if CBScaleZ.Value<0 then
        Current.Scale.Z:=-1/CBScaleZ.Value
     else
        Current.Scale.Z:=CBScaleZ.Value;
end;

procedure TChart3DEditor.CBTwoSideChange(Sender: TObject);
begin
  if not Changing then
     Current.TwoSide:=CBTwoSide.IsChecked;
end;

procedure TChart3DEditor.CBWidthChange(Sender: TObject);
begin
  if IReady and (not Changing) then
     Current.Width:=CBWidth.Value*0.01;
end;

procedure TChart3DEditor.CBWireChange(Sender: TObject);

  {$IFNDEF D17}
  procedure SetFillMode(const AControl:TFmxObject; AMode:TFillMode);
  var
    t: Integer;
  begin
    if AControl is TExtrudedShape3D then
    begin
      TExtrudedShape3D(AControl).{$IFDEF D17}MaterialBackSource{$ELSE}MaterialBack{$ENDIF}.FillMode:=AMode;
      TExtrudedShape3D(AControl).{$IFDEF D17}MaterialShaftSource{$ELSE}MaterialShaft{$ENDIF}.FillMode:=AMode;
    end;

    if AControl is TShape3D then
       TShape3D(AControl).{$IFDEF D17}MaterialSource{$ELSE}Material{$ENDIF}.FillMode:=AMode;

    for t := 0 to AControl.ChildrenCount-1 do
        SetFillMode(AControl.Children[t], AMode);
  end;
  {$ENDIF}

begin
  {$IFNDEF D17}
  if CBWire.IsChecked then
     SetFillMode(FControl, TFillMode.fmWireframe)
  else
     SetFillMode(FControl, TFillMode.fmSolid);

  RepaintViews;
  {$ENDIF}
end;

procedure TChart3DEditor.TreeView1Change(Sender: TObject);
var tmp : TControl3D;
    {$IFDEF D17}
    m : TMaterial;
    {$ENDIF}
begin
  VertScrollBox1.Enabled:=TreeView1.Selected<>nil;

  if not VertScrollBox1.Enabled then
     Exit;

  Changing:=True;

  tmp:=Current;

  if Assigned(tmp) then
     FAxes.Parent:=tmp
  else
     FAxes.Parent:=Viewport3D1;

  BReset.Visible:=TreeView1.Selected.ParentItem=nil;

  CBDiffuse.Enabled:=tmp is TShape3D;

  if CBDiffuse.Enabled then
  {$IFDEF D17}
     begin
       m:=TMaterialSource.ValidMaterial(TShape3DAccess(tmp).MaterialSource);

       if Assigned(m) then
          if m is TColorMaterial then
             CBDiffuse.Color:=TColorMaterial(m).Color
          else
          if m is TLightMaterial then
             CBDiffuse.Color:=TLightMaterial(m).Diffuse
          else
             CBDiffuse.Enabled:=False
       else
         CBDiffuse.Enabled:=False;
     end;
  {$ELSE}
     CBDiffuse.Color:=TShape3D(tmp).Material.Diffuse;
  {$ENDIF}

  CBBackDiffuse.Enabled:=tmp is TExtrudedShape3D;

  {$IFNDEF D17}
  if CBBackDiffuse.Enabled then
     CBBackDiffuse.Color:=TExtrudedShape3D(tmp).MaterialBack.Diffuse;
  {$ENDIF}

  BBitmap.Enabled:=CBDiffuse.Enabled;

  CBLighting.Enabled:=CBDiffuse.Enabled;

  {$IFNDEF D17}
  if CBLighting.Enabled then
     CBLighting.IsChecked:=TShape3D(tmp).Material.Lighting;
  {$ENDIF}

  LClass.Text:=tmp.ClassName;

  if (tmp.Name='') and Assigned(tmp.Parent) and (tmp.Parent.Name<>'') then
     LName.Text:=tmp.Parent.Name
  else
     LName.Text:=tmp.Name;

  CBClass.ItemIndex:=CBClass.ListBox.Items.IndexOf(Copy(tmp.ClassName,2,Length(tmp.ClassName)));
  CBClass.Enabled:=CBClass.ItemIndex<>-1;

  CBPosX.Value:=tmp.Position.X/0.01;
  CBPosY.Value:=tmp.Position.Y/0.01;
  CBPosZ.Value:=tmp.Position.Z/0.01;

  CBRotX.Value:=tmp.RotationAngle.X;
  CBRotY.Value:=tmp.RotationAngle.Y;
  CBRotZ.Value:=tmp.RotationAngle.Z;

  if tmp.Scale.X<1 then
     CBScaleX.Value:=-1/tmp.Scale.X
  else
     CBScaleX.Value:=tmp.Scale.X;

  if tmp.Scale.Y<1 then
     CBScaleY.Value:=-1/tmp.Scale.Y
  else
     CBScaleY.Value:=tmp.Scale.Y;

  if tmp.Scale.Z<1 then
     CBScaleZ.Value:=-1/tmp.Scale.Z
  else
     CBScaleZ.Value:=tmp.Scale.Z;

  CBHeight.Value:=tmp.Height/0.01;
  CBWidth.Value:=tmp.Width/0.01;
  CBDepth.Value:=tmp.Depth/0.01;

  CBOpacity.Value:=tmp.Opacity;
  CBTwoSide.IsChecked:=tmp.TwoSide;

  TeeFillCursors(CBCursor,tmp.Cursor);

  Viewport3D1.Repaint;

  Changing:=False;
end;

procedure TChart3DEditor.RepaintViews;
begin
  if Panel3DViews.Visible then
  begin
    ViewportTop.Repaint;
    ViewportFront.Repaint;
    ViewportSide.Repaint;
  end;
end;

procedure TChart3DEditor.TreeView1ChangeCheck(Sender: TObject);
begin
  Current.Visible:=TreeView1.Selected.IsChecked;

  if Current.Parent is TControl3D then
     TControl3D(Current.Parent).Repaint
  else
  if Current.Parent is TViewport3D then
     TViewport3D(Current.Parent).Repaint;

  RepaintViews;
end;

procedure TChart3DEditor.View3DViewsClick(Sender: TObject);
begin
  View3DViews.IsChecked:=not View3DViews.IsChecked;
  Panel3DViews.Visible:=View3DViews.IsChecked;
  Splitter3.Visible:=Panel3DViews.Visible;
end;

procedure TChart3DEditor.ViewObjectEditorClick(Sender: TObject);
begin
  ViewObjectEditor.IsChecked:=not ViewObjectEditor.IsChecked;
  VertScrollBox1.Visible:=ViewObjectEditor.IsChecked;
end;

procedure TChart3DEditor.Viewport3D1MouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
  RefreshViewport(Viewport3D1);
end;

procedure TChart3DEditor.Viewport3D1MouseUp(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Single);

  function MouseOver(const AControl:TControl3D):TControl3D;
  var t : Integer;
  begin
    if AControl.IsMouseOver then
       result:=AControl
    else
    begin
      result:=nil;

      for t := 0 to AControl.ChildrenCount-1 do
      if AControl.Children[t] is TControl3D then
      begin
        result:=MouseOver(TControl3D(AControl.Children[t]));
        if Assigned(result) then
           Exit;
      end;
    end;
  end;

  function NodeOf(const AItem:TTreeViewItem; const AControl:TControl3D):TTreeViewItem;
  var t : Integer;
  begin
    if AItem.TagObject=AControl then
       result:=AItem
    else
    begin
      result:=nil;

      for t:=0 to AItem.Count-1 do
      begin
        result:=NodeOf(AItem.Items[t],AControl);
        if Assigned(Result) then
           Break;
      end;
    end;
  end;

var tmp : TControl3D;
    tmpItem : TTreeViewItem;
    t : Integer;
begin
  tmp:=MouseOver(FControl);

  if Assigned(tmp) then
  begin
    for t:=0 to TreeView1.Count-1 do
    begin
      tmpItem:=NodeOf(TreeView1.Items[t],tmp);
      if Assigned(tmpItem) then
      begin
        TreeView1.Selected:=tmpItem;
        break;
      end;
    end;
  end;
end;

procedure TChart3DEditor.Viewport3D1Paint(Sender: TObject; Canvas: TCanvas;
  const ARect: TRectF);
begin
  RepaintViews;
end;

procedure TChart3DEditor.ViewportSideMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
  RefreshViewport(ViewportSide);
end;

procedure TChart3DEditor.ViewportFrontMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
  RefreshViewport(ViewportFront);
end;

procedure TChart3DEditor.ViewportTopMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
  RefreshViewport(ViewportTop);
end;

procedure TChart3DEditor.ViewportTopPaint(Sender: TObject; Canvas: TCanvas;
  const ARect: TRectF);
begin
  if Sender=CurrentViewport then
     TViewport3D(Sender).Canvas.DrawRect(TViewport3D(Sender).BoundsRect,6,6,AllCorners,1);
end;

procedure TChart3DEditor.DrawBounds(const Context:TContext3D; const AObject:TFmxObject;
                                    HoveredOnly:Boolean=False);
var
  t: Integer;
begin
  if AObject is TControl3D then
  with TControl3D(AObject) do
  begin
    Context.SetMatrix(AbsoluteMatrix);

    {$IFNDEF D17}
    Context.SetTextureUnit(0, nil);
    Context.SetContextState(TContextState.csLightOff);
    {$ENDIF}

    if IsMouseOver then
    begin
      {$IFDEF D17}
      Context.DrawCube({$IFDEF D20}Point3D{$ELSE}Vector3D{$ENDIF}(0, 0, 0),
                       {$IFDEF D20}Point3D{$ELSE}Vector3D{$ENDIF}(Width, Height, Depth), AbsoluteOpacity, TAlphaColors.Red);
      {$ELSE}
      Context.SetColor(TMaterialColor.mcDiffuse, TAlphaColors.Red);
      Context.DrawCube(Vector3D(0, 0, 0), Vector3D(Width, Height, Depth), AbsoluteOpacity);
      {$ENDIF}
    end
    else
    if AObject=Current then
    begin
      {$IFDEF D17}
      Context.DrawCube({$IFDEF D20}Point3D{$ELSE}Vector3D{$ENDIF}(0, 0, 0),
                       {$IFDEF D20}Point3D{$ELSE}Vector3D{$ENDIF}(Width, Height, Depth), AbsoluteOpacity, TAlphaColors.Blue);
      {$ELSE}
      Context.SetColor(TMaterialColor.mcDiffuse, TAlphaColors.Blue);
      Context.DrawCube(Vector3D(0, 0, 0), Vector3D(Width, Height, Depth), AbsoluteOpacity);
      {$ENDIF}
    end;
  end;

  for t := 0 to AObject.ChildrenCount-1 do
      DrawBounds(Context, AObject.Children[t], HoveredOnly);
end;

procedure TChart3DEditor.Dummy1Render(Sender: TObject; Context: TContext3D);
begin
  if Assigned(FControl) then
     DrawBounds(Viewport3D1.Context, FControl, True);
end;

procedure TChart3DEditor.RemoveOnMouseDown;

  procedure RemoveItems(AItem:TTreeViewItem);
  var tmp : TObject;
      t: Integer;
  begin
    tmp:=AItem.TagObject;
    if tmp is TControl3D then
       TControl3D(tmp).OnMouseUp:=nil;

    for t:=0 to AItem.Count-1 do
        RemoveItems(AItem.Items[t]);
  end;

var t : Integer;
begin
  for t := 0 to TreeView1.Count-1 do
      RemoveItems(TreeView1.Items[t]);
end;

procedure TChart3DEditor.MouseUp3D(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single;
    RayPos, RayDir: TVector3D);
begin
  Viewport3D1MouseUp(Sender,Button,Shift,X,Y);
  FMouse3D.MouseUp(Sender,Button,Shift,X,Y,NullVector3D,NullVector3D);
end;

procedure TChart3DEditor.FillTree(const Visual:TControl3D);

  procedure AddItems(const AParent:TTreeViewItem; const AObject:TFmxObject);
  var item : TTreeViewItem;
      t: Integer;
  begin
    item:=TTreeViewItem.Create(Self);
    item.TagObject:=AObject;
    item.IsChecked:=(AObject as TControl3D).Visible;

    (AObject as TControl3D).OnMouseUp:=MouseUp3D;

    if AObject.Name='' then
       item.Text:=AObject.ClassName
    else
       item.Text:=AObject.Name;

    if Assigned(AParent) then
       AParent.AddObject(item)
    else
       TreeView1.AddObject(item);

    for t := 0 to AObject.ChildrenCount-1 do
        if AObject.Children[t] is TControl3D then
           AddItems(item, AObject.Children[t]);
  end;

begin
  TreeView1.BeginUpdate;
  try
    TreeView1.Clear;
    AddItems(nil, Visual);
  finally
    TreeView1.EndUpdate;
  end;

  TreeView1.Items[0].IsExpanded:=True;
end;

procedure TChart3DEditor.FormCreate(Sender: TObject);
begin
  IReady:=True;

  // This setting here, XE7 can't read this property from DFM:
  CBOpacity.ValueType:=TNumValueType.{$IFDEF D20}Float{$ELSE}vtFloat{$ENDIF};

  {$IFDEF D17}
  CBWire.Visible:=False;
  {$ENDIF}
end;

procedure TChart3DEditor.FormDestroy(Sender: TObject);
begin
  Start.Free;
end;

procedure TChart3DEditor.MenuAxesClick(Sender: TObject);
begin
  MenuAxes.IsChecked:=not MenuAxes.IsChecked;
  FAxes.Visible:=MenuAxes.IsChecked;
end;

procedure TChart3DEditor.MenuItem2Click(Sender: TObject);
begin
  TAnimationsEditor.Edit(Self,Current);
end;

{ TMouse3D }
Constructor TMouse3D.Create(const AControl:TControl3D; const ACamera:TCamera=nil);
begin
  inherited Create;

  FCamera:=ACamera;
  FControl:=AControl;

  ITarget:=FCamera;
  if not Assigned(ITarget) then
     ITarget:=FControl;

  OldDown:=FControl.OnMouseDown;
  OldMove:=FControl.OnMouseMove;
  OldUp:=FControl.OnMouseUp;
  OldWheel:=FControl.OnMouseWheel;

  FControl.OnMouseDown:=MouseDown;
  FControl.OnMouseMove:=MouseMove;
  FControl.OnMouseUp:=MouseUp;
  FControl.OnMouseWheel:=MouseWheel;

  OldAuto:=FControl.AutoCapture;
  FControl.AutoCapture:=True;
end;

destructor TMouse3D.Destroy;
begin
  FControl.AutoCapture:=OldAuto;

  FControl.OnMouseDown:=OldDown;
  FControl.OnMouseMove:=OldMove;
  FControl.OnMouseUp:=OldUp;
  FControl.OnMouseWheel:=OldWheel;

  inherited;
end;

procedure TMouse3D.MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single; RayPos, RayDir: TVector3D);
begin
  Down[Button]:=True;

  if Assigned(FOnMouseDown) then
     FOnMouseDown(Sender,Button,Shift,X,Y,RayPos,RayDir);
end;

procedure TMouse3D.MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single;
  RayPos, RayDir: TVector3D);
begin
  if FControl.Parent is TViewport3D then
     if not TViewport3D(FControl.Parent).BoundsRect.Contains(PointF(X,Y)) then
     begin
       Down[TMouseButton.mbLeft]:=False;
       Down[TMouseButton.mbRight]:=False;
       Down[TMouseButton.mbMiddle]:=False;
     end;

  if Down[TMouseButton.mbLeft] then
  begin
    ITarget.RotationAngle.X:=ITarget.RotationAngle.X+(Y-Old.Y)*0.5;
    ITarget.RotationAngle.Y:=ITarget.RotationAngle.Y+(Old.X-X)*0.5;
  end
  else
  if Down[TMouseButton.mbRight] then
  begin
    ITarget.Position.X:=ITarget.Position.X+(X-Old.X)*0.02;
    ITarget.Position.Y:=ITarget.Position.Y+(Y-Old.Y)*0.02;
  end;

  Old.X:=X;
  Old.Y:=Y;

  if Assigned(FOnMouseMove) then
     FOnMouseMove(Sender,Shift,X,Y,RayPos,RayDir);
end;

procedure TMouse3D.MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single; RayPos, RayDir: TVector3D);
begin
  Down[Button]:=False;

  if Assigned(FOnMouseUp) then
     FOnMouseUp(Sender,Button,Shift,X,Y,RayPos,RayDir);
end;

procedure TMouse3D.MouseWheel(Sender: TObject; Shift: TShiftState;
  WheelDelta: Integer; var Handled: Boolean);
begin
  ITarget.Position.Z:=ITarget.Position.Z-WheelDelta*0.001;
end;

{ TObject3D }

procedure TObject3D.Embedd(const AObject: TFmxObject);
var c : Integer;
    tmp : TFmxObject;
begin
  DeleteChildren;

  c:=0;
  while c<AObject.ChildrenCount do
  begin
    tmp:=AObject.Children[c];

    if (tmp is TLight) or (tmp is TCamera) or (not (tmp is TControl3D)) then
       Inc(c)
    else
       AddObject(tmp);
  end;
end;

const
  TeeMsg_ErrorLoad3D='Error: FileName does not contain FireMonkey 3D controls (%s, %s)';
  TeeMsg_ErrorLoad3DNil='Error: FileName is empty (%s)';

procedure TObject3D.LoadFromFile(const FileName: String);
var f : TFileStream;
    s : TStream;
    c : TComponent;
    tmp : TStreamOriginalFormat;
begin
  f:=TFileStream.Create(FileName, fmOpenRead);
  try
    tmp:=TestStreamFormat(f);

    if tmp=sofBinary then
       s:=f
    else
    begin
      s:=TMemoryStream.Create;
      ObjectTextToResource(f,s);
      s.Position:=0;
    end;

    c:=TForm3D.CreateNew(Self);
    c:=s.ReadComponentRes(c);

    if s<>f then
       s.Free;

    if Assigned(c) then
    begin
      if c is TFmxObject then
         Embedd(TFmxObject(c))
      else
         raise Exception.CreateFmt(TeeMsg_ErrorLoad3D, [FileName,c.ClassName]);
    end
    else
       raise Exception.CreateFmt(TeeMsg_ErrorLoad3DNil,[FileName]);
  finally
    f.Free;
  end;
end;

{$IFDEF D20}
function TObject3D.RayCastIntersect(const RayPos, RayDir: TPoint3D; var Intersection: TPoint3D): Boolean;
{$ELSE}
function TObject3D.RayCastIntersect(const RayPos, RayDir: TVector3D;
  var Intersection: TVector3D): Boolean;
{$ENDIF}
begin
  result:=False;
end;

procedure TObject3D.Render;
const tmpColor = $FF60A799;
begin
  inherited;

  if csDesigning in ComponentState then
  begin
    {$IFDEF D17}
    Context.DrawCube({$IFDEF D20}Point3D{$ELSE}Vector3D{$ENDIF}(0, 0, 0),
                     {$IFDEF D20}Point3D{$ELSE}Vector3D{$ENDIF}(0.8, 0.8, 0.8), 1, tmpColor);
    {$ELSE}
    Context.SetContextState(TContextState.csLightOff);
    Context.SetContextState(TContextState.csTexDisable);
    Context.SetColor(TMaterialColor.mcDiffuse, tmpColor);
    Context.DrawCube(Vector3D(0, 0, 0), Vector3D(0.8, 0.8, 0.8), 1);
    {$ENDIF}
  end;
end;

procedure TObject3D.SetFileName(const Value: String);
begin
  if FFileName<>Value then
  begin
    FFileName:=Value;

    if FFileName<>'' then
       LoadFromFile(FFileName);
  end;
end;

procedure TObject3D.SetVisible(const Value: Boolean);
begin
  inherited;
  Repaint;
end;

{ TAxis3D }

constructor TAxis3D.Create(AOwner: TComponent);
{$IFDEF D17}
var m : TLightMaterialSource;
{$ENDIF}
begin
  inherited;
  FFont := TFont.Create;
  FFont.Size:=7;
  FFont.OnChanged := FontChanged;

  {$IFDEF D17}
  m:=TLightMaterialSource.Create(Self);
  m.Diffuse:=claRed;
  MaterialSource:=m;
  {$ELSE}
  Material.Diffuse:=claRed;
  {$ENDIF}
end;

destructor TAxis3D.Destroy;
begin
  FFont.Free;
  inherited;
end;

procedure TAxis3D.FontChanged(Sender: TObject);
begin
  Repaint;
end;

{$IFDEF D20}
function TAxis3D.RayCastIntersect(const RayPos, RayDir: TPoint3D; var Intersection: TPoint3D): Boolean;
{$ELSE}
function TAxis3D.RayCastIntersect(const RayPos, RayDir: TVector3D;
  var Intersection: TVector3D): Boolean;
{$ENDIF}
begin
  result:=False;
end;

procedure TAxis3D.Render;

  procedure TextOut(const APosition:{$IFDEF D20}TPoint3D{$ELSE}TVector3D{$ENDIF}; const S:String; AAlign:TTextAlign);
  var Path : TPathData;
      r : TRectF;
      B : TPointF;
      VP : TPolygon;
  begin
    Path:=TPathData.Create;
    try
      r:= RectF(0, 0, Width * 10, Height * 10);

      if {$IFDEF D17}TCanvasManager.MeasureCanvas{$ELSE}GetMeasureBitmap.Canvas{$ENDIF}.TextToPath(Path,r,S,False,AAlign,
                     TTextAlign.{$IFDEF D20}Center{$ELSE}taCenter{$ENDIF}) then
      begin
        B := Path.FlattenToPolygon(VP, 1);

        if (B.X > 0) and (B.Y > 0) then
        begin
          Context.FillPolygon(APosition,
                        {$IFDEF D20}Point3D{$ELSE}Vector3D{$ENDIF}(5*Width / Font.Size,
                                                                   5*Height / Font.Size, Depth),
                        r, VP,
                        {$IFDEF D17}TMaterialSource.ValidMaterial(MaterialSource),{$ENDIF}
                        AbsoluteOpacity, True, True, False);
        end;
      end;
    finally
      Path.Free;
    end;
  end;

const
  Min=-10;
  Max=+10;
  MinorTick=0.25;
  MajorTick=0.4;

var tmp,
    tmpTick : Single;
    Tick  : Integer;
    c : TCanvas;
begin
  inherited;

  c:={$IFDEF D17}TCanvasManager.MeasureCanvas{$ELSE}GetMeasureBitmap.Canvas{$ENDIF};

  if c.BeginScene then
  begin
    c.Font.Family := Font.Family;
    c.Font.Style := Font.Style;
    c.Font.Size := Font.Size;
  end;

  {$IFNDEF D17}
  Context.SetContextState(TContextState.csLightOff);
  Context.SetContextState(TContextState.csTexDisable);
  Context.SetColor(TMaterialColor.mcDiffuse, claNavy);
  {$ENDIF}

  Context.DrawLine({$IFDEF D20}Point3D{$ELSE}Vector3D{$ENDIF}(Min, 0, 0), {$IFDEF D20}Point3D{$ELSE}Vector3D{$ENDIF}(Max, 0, 0), 1 {$IFDEF D17}, claNavy{$ENDIF});
  Context.DrawLine({$IFDEF D20}Point3D{$ELSE}Vector3D{$ENDIF}(0, Min, 0), {$IFDEF D20}Point3D{$ELSE}Vector3D{$ENDIF}(0, Max, 0), 1 {$IFDEF D17}, claNavy{$ENDIF});
  Context.DrawLine({$IFDEF D20}Point3D{$ELSE}Vector3D{$ENDIF}(0, 0, Min), {$IFDEF D20}Point3D{$ELSE}Vector3D{$ENDIF}(0, 0, Max), 1 {$IFDEF D17}, claNavy{$ENDIF});

  {$IFNDEF D17}
  Context.SetMaterial(Material);
  {$ENDIF}

  Tick:=0;
  tmp:=Min;

  while tmp<=Max do
  begin
    if Tick mod 10=0 then
       tmpTick:=MajorTick
    else
       tmpTick:=MinorTick;

    Context.DrawLine({$IFDEF D20}Point3D{$ELSE}Vector3D{$ENDIF}(tmp, 0, 0), {$IFDEF D20}Point3D{$ELSE}Vector3D{$ENDIF}(tmp, tmpTick, 0), 1 {$IFDEF D17}, claNavy{$ENDIF});
    Context.DrawLine({$IFDEF D20}Point3D{$ELSE}Vector3D{$ENDIF}(0, tmp, 0), {$IFDEF D20}Point3D{$ELSE}Vector3D{$ENDIF}(-tmpTick, tmp, 0), 1 {$IFDEF D17}, claNavy{$ENDIF});
    Context.DrawLine({$IFDEF D20}Point3D{$ELSE}Vector3D{$ENDIF}(0, 0, tmp), {$IFDEF D20}Point3D{$ELSE}Vector3D{$ENDIF}(0, tmpTick, tmp), 1 {$IFDEF D17}, claNavy{$ENDIF});  // Z

    if Tick mod 10=0 then
    begin
      TextOut({$IFDEF D20}Point3D{$ELSE}Vector3D{$ENDIF}(tmp,0.5,0), FloatToStr(tmp), TTextAlign.{$IFDEF D20}Center{$ELSE}taCenter{$ENDIF});
      TextOut({$IFDEF D20}Point3D{$ELSE}Vector3D{$ENDIF}(-0.5,tmp,0), FloatToStr(tmp), TTextAlign.{$IFDEF D20}Trailing{$ELSE}taTrailing{$ENDIF});
      TextOut({$IFDEF D20}Point3D{$ELSE}Vector3D{$ENDIF}(0.5,0,tmp), FloatToStr(tmp), TTextAlign.{$IFDEF D20}Leading{$ELSE}taLeading{$ENDIF});
    end;

    Inc(Tick);

    tmp:=tmp+0.5;
  end;

  c.EndScene;
end;

procedure TAxis3D.SetFont(const Value: TFont);
begin
  FFont.Assign(Value);
end;

procedure TAxis3D.SetVisible(const Value: Boolean);
begin
  inherited;
  Repaint;
end;

end.
