unit ClientVisuals;

interface

uses
  Windows, Messages, SysUtils, Types, UITypes, Classes, ASVisualization,
  MainForm, ASKernelDefs, Generics.Defaults, Generics.Collections, Controls,
  Forms, VisForm, ASObjects, Graphics, Menus, ClientDefs, DoublePoint, VisCtl,
  rgl, VisCtl2D;

type
  EClientVisualException = class(Exception);

  TManagedVisCtl2D = class(TVisCtl2D)
  strict private
    FDiagramName: string;
    class var FInstances: TDictionary<string, TManagedVisCtl2D>;
    class constructor ClassCreate;
    class destructor ClassDestroy;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property DiagramName: string read FDiagramName;
    class property Instances: TDictionary<string, TManagedVisCtl2D> read FInstances;
  end;

  TTemporaryVisCtl2D = class(TVisCtl2D);

  TManagedVisCtl3D = class(TVisCtl3D)
  strict private
    FSceneName: string;
    class var FInstances: TDictionary<string, TManagedVisCtl3D>;
    class constructor ClassCreate;
    class destructor ClassDestroy;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property SceneName: string read FSceneName;
    class property Instances: TDictionary<string, TManagedVisCtl3D> read FInstances;
  end;

  TTemporaryVisCtl3D = class(TVisCtl3D);

  TVisualRec = class
    ID: TGUID;
    Name: string;
    ClassType: TVisObjClass;
    Title,
    Description: string;
    LastPaintTime: Double;
  end;

  TVisualization = record
  strict private
    class var FDiagram: string;
    class var FScene: string;
    class var FPopups: Boolean;
    class var FDiagramNumber: UInt64;
    class var FSceneNumber: UInt64;
    class function BarChart(ACtl: TVisCtl2D; AVisual: TVisual): TDrawable; static;
    class function PieChart(ACtl: TVisCtl2D; AVisual: TVisual): TDrawable; static;
    class function Histogram(ACtl: TVisCtl2D; AVisual: TVisual): TDrawable; static;
    class function ScatterPlot(ACtl: TVisCtl2D; AVisual: TVisual): TDrawable; overload; static;
    class function ScatterPlot(ACtl: TVisCtl3D; AVisual: TVisual): TDrawable3D; overload; static;
    class function Surface(ACtl: TVisCtl3D; AVisual: TVisual): TDrawable3D; static;
    class function SpaceCurve(ACtl: TVisCtl3D; AVisual: TVisual): TDrawable3D; static;
    class function Object3D(ACtl: TVisCtl3D; AVisual: TVisual): TDrawable3D; static;
    class function RegionPlot(ACtl: TVisCtl2D; AVisual: TVisual): TDrawable; static;
    class function Heatmap(ACtl: TVisCtl2D; AVisual: TVisual): TDrawable; static;
    class function VectorField(ACtl: TVisCtl2D; AVisual: TVisual): TDrawable; overload; static;
    class function VectorField(ACtl: TVisCtl3D; AVisual: TVisual): TDrawable3D; overload; static;
    class function Line(ACtl: TVisCtl2D; AVisual: TVisual): TDrawable; static;
    class function Rectangle(ACtl: TVisCtl2D; AVisual: TVisual): TDrawable; static;
    class function Circle(ACtl: TVisCtl2D; AVisual: TVisual): TDrawable; static;
    class function Polygon(ACtl: TVisCtl2D; AVisual: TVisual): TDrawable; static;
    class function Text(ACtl: TVisCtl2D; AVisual: TVisual): TDrawable; overload; static;
    class function Text(ACtl: TVisCtl3D; AVisual: TVisual): TDrawable3D; overload; static;
    class function Pixmap(ACtl: TVisCtl2D; AVisual: TVisual): TDrawable; overload; static;
    class function Pixmap(ACtl: TVisCtl3D; AVisual: TVisual): TDrawable3D; overload; static;
    class function Arrow(ACtl: TVisCtl2D; AVisual: TVisual): TDrawable; overload; static;
    class function Arrow(ACtl: TVisCtl3D; AVisual: TVisual): TDrawable3D; overload; static;
    class function GetDrawableByGUID(const AGUID: TGUID): TVisObj; static;
  public
    class constructor ClassCreate;
    class procedure Visualize(AVisual: TVisual; ARef: PAlgosimReference = nil); static;
    class procedure RemoveVisual(const AGUID: TGUID); static;
    class procedure ConfigVisual(const AGUID: TGUID; ASettings: TAlgosimStructure); static;
    class procedure ExportVisual(const AGUID: TGUID; const AFileName: TFileName;
      ASettings: TAlgosimStructure); static;
    class function QueryVisual(var AVisualRec: TVisualRec): Boolean; static;
    class procedure EnumVisuals(AList: TList<TGUID>); static;
    class property Diagram: string read FDiagram write FDiagram;
    class property Scene: string read FScene write FScene;
    class property Popups: Boolean read FPopups write FPopups;
    class function GetDiagram(out ANewDiagram: Boolean): TVisCtl2D; static;
    class function GetScene(out ANewScene: Boolean): TVisCtl3D; static;
    class function ShowDiagram(ADiagram: TDiagram): Boolean; overload; static;
    class function ShowDiagram(ADiagram: TVisCtl2D): Boolean; overload; static;
    class function ShowDiagram(const ADiagram: string): Boolean; overload; static;
    class function ShowDiagram(const ADiagram: TGUID): Boolean; overload; static;
    class function ShowScene(AScene: TScene): Boolean; overload; static;
    class function ShowScene(AScene: TVisCtl3D): Boolean; overload; static;
    class function ShowScene(const AScene: string): Boolean; overload; static;
    class function ShowScene(const AScene: TGUID): Boolean; overload; static;
    class function ShowVisCtl(const AID: TGUID): Boolean; static;
  end;

implementation

uses
  ASNum, Gallery, Math, ImageSizeForm, UxPanel, ASConsole;

{ TVisualization }

class function TVisualization.Arrow(ACtl: TVisCtl3D;
  AVisual: TVisual): TDrawable3D;
var
  Arrow: TArrow absolute Result;
begin
  Arrow := TArrow.Create(ACtl);
  var Data := AVisual.Data as TArrowDataR3;
  Arrow.Position := Data.a;
  Arrow.Vector := Data.v;
  Arrow.Color := clRed;
end;

class function TVisualization.Arrow(ACtl: TVisCtl2D;
  AVisual: TVisual): TDrawable;
var
  Line: TLine absolute Result;
begin
  Line := TLine.Create(ACtl, ACtl.View);
  var Data := AVisual.Data as TArrowDataR2;
  Line.Start := Data.a;
  Line.&End := Data.a + Data.v;
  Line.EndMarker.Kind := lemSemiArrow;
end;

class function TVisualization.BarChart(ACtl: TVisCtl2D; AVisual: TVisual): TDrawable;
var
  BarChart: TBarChart absolute Result;
  CatData: TCategoryData;
begin
  BarChart := TBarChart.Create(ACtl, ACtl.View);
  for CatData in AVisual.Data as TCategoryDataList do
    BarChart.AddBar(CatData.Name, CatData.Value);
end;

{$IF SizeOf(ASR) = SizeOf(Double)}
type
  ASRArrayToDoubleArray = TArray<Double>;
  ASR2ArrayToDoublePointArray = TArray<TPointD>;
{$ELSE}
function ASRArrayToDoubleArray(const A: TArray<TASR>): TArray<Double>;
begin
  SetLength(Result, Length(A));
  for var i := 0 to High(A) do
    Result[i] := A[i];
end;

function ASR2ArrayToDoublePointArray(const A: TArray<TASR2>): TArray<TPointD>;
begin
  SetLength(Result, Length(A));
  for var i := 0 to High(A) do
  begin
    Result[i].X := A[i].X;
    Result[i].Y := A[i].Y;
  end;
end;
{$ENDIF}

class function TVisualization.Heatmap(ACtl: TVisCtl2D;
  AVisual: TVisual): TDrawable;
var
  Heatmap: THeatmap absolute Result;
begin
  Heatmap := THeatmap.Create(ACtl, ACtl.View);
  with AVisual.Data as THeatmapData do
  begin
    var bm := Pixmap.CreateGDIBitmap;
    try
      Heatmap.Bitmap := bm;
    finally
      bm.Free;
    end;
    Heatmap.Rect := TRectD.Create(x0, y1, x1, y0);
  end;
end;

class function TVisualization.Histogram(ACtl: TVisCtl2D; AVisual: TVisual): TDrawable;
var
  Histogram: THistogram absolute Result;
begin
  Histogram := THistogram.Create(ACtl, ACtl.View);
  Histogram.Data := ASRArrayToDoubleArray((AVisual.Data as THistogramData).Numbers);
end;

class function TVisualization.Line(ACtl: TVisCtl2D;
  AVisual: TVisual): TDrawable;
var
  Line: TLine absolute Result;
begin
  Line := TLine.Create(ACtl, ACtl.View);
  Line.Start := (AVisual.Data as TLineDataR2).a;
  Line.&End := (AVisual.Data as TLineDataR2).b;
end;

class function TVisualization.Circle(ACtl: TVisCtl2D;
  AVisual: TVisual): TDrawable;
var
  Circle: TCircle absolute Result;
begin
  Circle := TCircle.Create(ACtl, ACtl.View);
  Circle.Center := (AVisual.Data as TCircleData).a;
  Circle.Radius := (AVisual.Data as TCircleData).r;
end;

class constructor TVisualization.ClassCreate;
begin
  FPopups := True;
end;

class procedure TVisualization.ConfigVisual(const AGUID: TGUID;
  ASettings: TAlgosimStructure);
var
  Drawable: TDrawable;
  Drawable3D: TDrawable3D;
begin
  if TDrawable.TryGetDrawableByGUID(AGUID, Drawable) then
  begin
    Drawable.Control.BeginBackgroundPaint;
    try
      if Assigned(ASettings) then
        Drawable.Configure(ASettings)
      else
        Drawable.ShowOptionsForm(AlgosimMainForm);
    finally
      Drawable.Control.EndBackgroundPaint;
    end;
  end
  else if TDrawable3D.TryGetDrawableByGUID(AGUID, Drawable3D) then
  begin
    Drawable3D.Control.BeginBackgroundPaint;
    try
      if Assigned(ASettings) then
        Drawable3D.Configure(ASettings)
      else
        Drawable3D.ShowOptionsForm(AlgosimMainForm);
    finally
      Drawable3D.Control.EndBackgroundPaint;
    end;
  end;
end;

class procedure TVisualization.EnumVisuals(AList: TList<TGUID>);
begin

  for var D in TDrawable.Instances do
    AList.Add(D.Key);

  for var D in TDrawable3D.Instances do
    AList.Add(D.Key);

end;

class procedure TVisualization.ExportVisual(const AGUID: TGUID;
  const AFileName: TFileName; ASettings: TAlgosimStructure);
var
  Drawable: TDrawable;
  Drawable3D: TDrawable3D;
begin
  if TDrawable.TryGetDrawableByGUID(AGUID, Drawable) then
  begin
    if string(AFileName).IsEmpty then
      Drawable.Control.SaveAsSVG
    else
    begin
      var LOptions := DefaultSVGExportOptions;
      var LWidth, LHeight: string;
      if ASettings.HasMember('width') then
        LWidth := ASettings['width'].ToString;
      if ASettings.HasMember('height') then
        LHeight := ASettings['height'].ToString;
      LOptions.SetDimensionsFromText(LWidth, LHeight);
      if ASettings.HasMember('stretch') then
        LOptions.Stretch := ASettings['stretch'].ToBoolean;
      if ASettings.HasMember('title') then
        LOptions.Title := ASettings['title'].ToString;
      if ASettings.HasMember('description') then
        LOptions.Description := ASettings['description'].ToString;
      if ASettings.HasMember('language') then
        LOptions.Language := ASettings['language'].ToString;
      Drawable.Control.SaveAsSVG(AFileName, LOptions);
    end;
  end
  else if TDrawable3D.TryGetDrawableByGUID(AGUID, Drawable3D) then
  begin
    var LWidth := Drawable3D.Control.ClientWidth;
    var LHeight := Drawable3D.Control.ClientHeight;
    if ASettings.HasMember('width') then
      LWidth := ASettings['width'].ToInteger;
    if ASettings.HasMember('height') then
      LHeight := ASettings['height'].ToInteger;
    Drawable3D.Control.SaveToBitmap(AFileName, LWidth, LHeight);
  end;
end;

class function TVisualization.GetDiagram(out ANewDiagram: Boolean): TVisCtl2D;
var
  LCtl: TManagedVisCtl2D;
begin

  if FDiagram.IsEmpty then
  begin
    Inc(FDiagramNumber);
    var LFrm := TUxForm.CreateNewForm<TDiagramForm>;
    LFrm.Caption := 'Unnamed diagram'#32 + FDiagramNumber.ToString;
    ANewDiagram := True;
    Result := LFrm.VisCtl2D;
  end
  else if TManagedVisCtl2D.Instances.TryGetValue(FDiagram, LCtl) then
  begin
    ANewDiagram := False;
    Result := LCtl;
  end
  else
  begin
    var LFrm := TUxForm.CreateNewForm<TManagedDiagramForm>;
    LFrm.Caption := FDiagram;
    ANewDiagram := True;
    Result := LFrm.VisCtl2D;
  end;

end;

class function TVisualization.GetDrawableByGUID(const AGUID: TGUID): TVisObj;
var
  Drawable: TDrawable;
  Drawable3D: TDrawable3D;
begin
  if TDrawable.TryGetDrawableByGUID(AGUID, Drawable) then
    Result := Drawable
  else if TDrawable3D.TryGetDrawableByGUID(AGUID, Drawable3D) then
    Result := Drawable3D
  else
    Result := nil;
end;

class function TVisualization.GetScene(out ANewScene: Boolean): TVisCtl3D;
var
  LCtl: TManagedVisCtl3D;
begin

  if FScene.IsEmpty then
  begin
    Inc(FSceneNumber);
    var LFrm := TUxForm.CreateNewForm<TSceneForm>;
    LFrm.Caption := 'Unnamed scene'#32 + FSceneNumber.ToString;
    ANewScene := True;
    Result := LFrm.VisCtl3D;
  end
  else if TManagedVisCtl3D.Instances.TryGetValue(FScene, LCtl) then
  begin
    ANewScene := False;
    Result := LCtl;
  end
  else
  begin
    var LFrm := TUxForm.CreateNewForm<TManagedSceneForm>;
    LFrm.Caption := FScene;
    ANewScene := True;
    Result := LFrm.VisCtl3D;
  end;

end;

class function TVisualization.PieChart(ACtl: TVisCtl2D; AVisual: TVisual): TDrawable;
var
  PieChart: TPieChart absolute Result;
  CatData: TCategoryData;
begin
  PieChart := TPieChart.Create(ACtl, ACtl.View);
  for CatData in AVisual.Data as TCategoryDataList do
    PieChart.AddSlice(CatData.Name, CatData.Value);
end;

class function TVisualization.Pixmap(ACtl: TVisCtl3D;
  AVisual: TVisual): TDrawable3D;
var
  Pixmap: TImageRect absolute Result;
begin
  Pixmap := TImageRect.Create(ACtl);
  var bm := (AVisual.Data as TPixmapData).Pixmap.CreateGDIBitmap;
  try
    Pixmap.Bitmap := bm
  finally
    bm.Free;
  end;
end;

class function TVisualization.Pixmap(ACtl: TVisCtl2D;
  AVisual: TVisual): TDrawable;
var
  Pixmap: TPixmap absolute Result;
begin
  Pixmap := TPixmap.Create(ACtl, ACtl.View);
  Pixmap.Rect := (AVisual.Data as TPixmapData).Rect;
  var bm := (AVisual.Data as TPixmapData).Pixmap.CreateGDIBitmap;
  try
    Pixmap.Bitmap := bm
  finally
    bm.Free;
  end;
end;

class function TVisualization.Polygon(ACtl: TVisCtl2D;
  AVisual: TVisual): TDrawable;
var
  Polygon: TPolygon absolute Result;
begin
  Polygon := TPolygon.Create(ACtl, ACtl.View);
  Polygon.Points := ASR2ArrayToDoublePointArray((AVisual.Data as TPolygonData).Points)
end;

class function TVisualization.QueryVisual(var AVisualRec: TVisualRec): Boolean;
begin

  var D := GetDrawableByGUID(AVisualRec.ID);

  Result := Assigned(D) and (D.GUID = AVisualRec.ID);

  if Result then
  begin
    AVisualRec.Name := D.Name;
    if D.ClassType.InheritsFrom(TVisObj) then
      AVisualRec.ClassType := TVisObjClass(D.ClassType);
    AVisualRec.Title := D.Title;
    AVisualRec.Description := D.Description;
    AVisualRec.LastPaintTime := D.LastDrawTime / 1000;
  end;

end;

class function TVisualization.Rectangle(ACtl: TVisCtl2D;
  AVisual: TVisual): TDrawable;
var
  Rectangle: TRectangle absolute Result;
begin
  Rectangle := TRectangle.Create(ACtl, ACtl.View);
  with AVisual.Data as TRectangleData do
    Rectangle.Rect := TRectD.Create(a.X, a.Y - h, a.X + w, a.Y);
end;

class function TVisualization.RegionPlot(ACtl: TVisCtl2D; AVisual: TVisual): TDrawable;
var
  RegionPlot: TRegion absolute Result;
begin
  RegionPlot := TRegion.Create(ACtl, ACtl.View);
  with AVisual.Data as TRegionDataR2 do
  begin
    RegionPlot.SliceData := Slices;
    RegionPlot.Axis := Axis;
    RegionPlot.UnboundedMin := UnboundedMin;
    RegionPlot.UnboundedMax := UnboundedMax;
  end;
  RegionPlot.Points := False;
  RegionPlot.Lines := False;
end;

class procedure TVisualization.RemoveVisual(const AGUID: TGUID);
var
  Drawable: TDrawable;
  Drawable3D: TDrawable3D;
begin
  if TDrawable.TryGetDrawableByGUID(AGUID, Drawable) then
  begin
    if TDrawable.ModalLevel > 0 then
      raise EClientVisualException.Create('Cannot remove a drawable object when a settings dialog is open.');
    if Drawable is TDiagram then
    begin
      Drawable.Control.Free;
    end
    else
    begin
      Drawable.Control.BeginBackgroundPaint;
      try
        Drawable.Control.RemoveObject(Drawable);
      finally
        Drawable.Control.EndBackgroundPaint;
      end;
    end;
  end
  else if TDrawable3D.TryGetDrawableByGUID(AGUID, Drawable3D) then
  begin
    if TDrawable3D.ModalLevel > 0 then
      raise EClientVisualException.Create('Cannot remove a drawable object when a settings dialog is open.');
    if Drawable3D is TScene then
    begin
      Drawable3D.Control.Free;
    end
    else
    begin
      Drawable3D.Control.BeginBackgroundPaint;
      try
        Drawable3D.Control.RemoveObject(Drawable3D);
      finally
        Drawable3D.Control.EndBackgroundPaint;
      end;
    end;
  end;
end;

class function TVisualization.ScatterPlot(ACtl: TVisCtl2D; AVisual: TVisual): TDrawable;
var
  ScatterPlot: TXYPlot absolute Result;
begin
  ScatterPlot := TXYPlot.Create(ACtl, ACtl.View);
  ScatterPlot.Data := ASR2ArrayToDoublePointArray((AVisual.Data as TScatterDataR2).Points);
  ScatterPlot.Metadata := TScatterDataR2(AVisual.Data).Metadata;    // ToO; unsafe cast after safe cast
  TScatterDataR2(AVisual.Data).Metadata := nil;
end;

class function TVisualization.ScatterPlot(ACtl: TVisCtl3D; AVisual: TVisual): TDrawable3D;
var
  SimpleScatterPlot: TSimpleScatterPlot absolute Result;
  AdvScatterPlot: TAdvScatterPlot absolute Result;
begin
  if AVisual.Data is TScatterDataR3 then
  begin
    SimpleScatterPlot := TSimpleScatterPlot.Create(ACtl);
    SimpleScatterPlot.DataAsDoubles := (AVisual.Data as TScatterDataR3).Points;
  end
  else if AVisual.Data is TScatterDataR3cs then
  begin
    AdvScatterPlot := TAdvScatterPlot.Create(ACtl);
    AdvScatterPlot.DataAsDoubles := (AVisual.Data as TScatterDataR3cs).Points;
  end;
end;

class function TVisualization.ShowDiagram(ADiagram: TVisCtl2D): Boolean;
begin
  Result := ShowDiagram(ADiagram.Diagram);
end;

class function TVisualization.ShowDiagram(ADiagram: TDiagram): Boolean;
begin
  var LCtl := ADiagram.Control;
  Result := Assigned(LCtl);
  if Result then
  begin
    var LFrm := GetParentForm(LCtl);
    if Assigned(LFrm) then
      LFrm.Show
    else if LCtl is TManagedVisCtl2D then
    begin
      LFrm := TUxForm.CreateNewForm<TManagedDiagramForm>(LCtl);
      LFrm.Caption := TManagedVisCtl2D(LCtl).DiagramName;
    end;
  end;
end;

class function TVisualization.ShowScene(AScene: TVisCtl3D): Boolean;
begin
  Result := ShowScene(AScene.Scene);
end;

class function TVisualization.ShowScene(AScene: TScene): Boolean;
begin
  var LCtl := AScene.Control;
  Result := Assigned(LCtl);
  if Result then
  begin
    var LFrm := GetParentForm(LCtl);
    if Assigned(LFrm) then
      LFrm.Show
    else if LCtl is TManagedVisCtl3D then
    begin
      LFrm := TUxForm.CreateNewForm<TManagedSceneForm>(LCtl);
      LFrm.Caption := TManagedVisCtl3D(LCtl).SceneName;
    end;
  end;
end;

class function TVisualization.VectorField(ACtl: TVisCtl2D;
  AVisual: TVisual): TDrawable;
var
  VectorField: TVectorField absolute Result;
  VFD: TVectorFieldDataR2;
begin
  VFD := AVisual.Data as TVectorFieldDataR2;
  VectorField := TVectorField.Create(ACtl, ACtl.View);
  VectorField.BeginAddVector;
  try
    for var i := 0 to High(VFD.Vectors) do
      VectorField.AddVector(VFD.Vectors[i].Key, VFD.Vectors[i].Value);
  finally
    VectorField.EndAddVector;
  end;
end;

class function TVisualization.VectorField(ACtl: TVisCtl3D;
  AVisual: TVisual): TDrawable3D;
var
  VectorField: rgl.TVectorField absolute Result;
  VFD: TVectorFieldDataR3;
begin
  VFD := AVisual.Data as TVectorFieldDataR3;
  VectorField := rgl.TVectorField.Create(ACtl);
  VectorField.Data := VFD.Vectors;
  VectorField.PerVertexColors := VFD.Colored;
end;

class procedure TVisualization.Visualize(AVisual: TVisual; ARef: PAlgosimReference);
begin

  case AVisual.Target of
    vt2D:
      begin

        var NewDiagram: Boolean;
        var Ctl := GetDiagram(NewDiagram);

        if Ctl = nil then
          raise EClientVisualException.Create('No diagram control.');

        Ctl.BeginBackgroundPaint;
        try

          var Obj := TDrawable(nil);

          case AVisual.Kind of
            vkNull: ;
            vkBarChart:
              Obj := BarChart(Ctl, AVisual);
            vkPieChart:
              Obj := PieChart(Ctl, AVisual);
            vkHistogram:
              Obj := Histogram(Ctl, AVisual);
            vkXYPlot:
              Obj := ScatterPlot(Ctl, AVisual);
            vkRegion:
              Obj := RegionPlot(Ctl, AVisual);
            vkHeatmap:
              Obj := Heatmap(Ctl, AVisual);
            vkVectorField:
              Obj := VectorField(Ctl, AVisual);
            vkPixmap:
              Obj := Pixmap(Ctl, AVisual);
            vkText:
              Obj := Text(Ctl, AVisual);
            vkLine:
              Obj := Line(Ctl, AVisual);
            vkRectangle:
              Obj := Rectangle(Ctl, AVisual);
            vkCircle:
              Obj := Circle(Ctl, AVisual);
            vkPolygon:
              Obj := Polygon(Ctl, AVisual);
            vkArrow:
              Obj := Arrow(Ctl, AVisual);
          end;

          if Assigned(Obj) then
          begin
            Ctl.AddObject(Obj);
            if NewDiagram and Assigned(AVisual.ViewSetupProc2D) then
              AVisual.ViewSetupProc2D(Ctl, Obj);
            if Assigned(AVisual.OwnSetupProc2D) then
              AVisual.OwnSetupProc2D(Ctl, Obj);
            if Assigned(ARef) then
              ARef^ := Obj.CreateReference;
          end;

          if (Ctl.View.AspectDeviation < 2.0) and Assigned(Obj) and Obj.AllowAutoNormalization then
            Ctl.AutoNormalize := True;

        finally
          Ctl.EndBackgroundPaint;
        end;

      end;
    vt3D:
      begin

        var NewScene: Boolean;
        var Ctl := GetScene(NewScene);

        if Ctl = nil then
          raise EClientVisualException.Create('No visualization control.');

        Ctl.BeginBackgroundPaint;
        try

          var Obj := TDrawable3D(nil);

          case AVisual.Kind of
            vkNull: ;
            vkXYZPlot, vkXYZcsPlot:
              Obj := ScatterPlot(Ctl, AVisual);
            vkSurface:
              Obj := Surface(Ctl, AVisual);
            vkSpaceCurve:
              Obj := SpaceCurve(Ctl, AVisual);
            vkObject3D:
              Obj := Object3D(Ctl, AVisual);
            vkVectorField:
              Obj := VectorField(Ctl, AVisual);
            vkPixmap:
              Obj := Pixmap(Ctl, AVisual);
            vkText:
              Obj := Text(Ctl, AVisual);
            vkArrow:
              Obj := Arrow(Ctl, AVisual);
          end;

          if Assigned(Obj) then
          begin
            Ctl.AddObject(Obj);
            if NewScene and Assigned(AVisual.ViewSetupProc3D) then
              AVisual.ViewSetupProc3D(Ctl, Obj);
            if Assigned(AVisual.OwnSetupProc3D) then
              AVisual.OwnSetupProc3D(Ctl, Obj);
            if Assigned(ARef) then
              ARef^ := Obj.CreateReference;
          end;

        finally
          Ctl.EndBackgroundPaint;
        end;

      end;
  end;

end;

class function TVisualization.ShowDiagram(const ADiagram: string): Boolean;
var
  Ctl: TManagedVisCtl2D;
begin
  Result := TManagedVisCtl2D.Instances.TryGetValue(ADiagram, Ctl);
  if Result then
    ShowDiagram(Ctl);
end;

class function TVisualization.ShowDiagram(const ADiagram: TGUID): Boolean;
var
  Drawable: TDrawable;
begin
  Result := TDrawable.TryGetDrawableByGUID(ADiagram, Drawable);
  if Result then
    ShowDiagram(Drawable.Control);
end;

class function TVisualization.ShowScene(const AScene: string): Boolean;
var
  Ctl: TManagedVisCtl3D;
begin
  Result := TManagedVisCtl3D.Instances.TryGetValue(AScene, Ctl);
  if Result then
    ShowScene(Ctl);
end;

class function TVisualization.ShowScene(const AScene: TGUID): Boolean;
var
  Drawable: TDrawable3D;
begin
  Result := TDrawable3D.TryGetDrawableByGUID(AScene, Drawable);
  if Result then
    ShowScene(Drawable.Control);
end;

class function TVisualization.ShowVisCtl(const AID: TGUID): Boolean;
var
  Drawable2D: TDrawable;
  Drawable3D: TDrawable3D;
begin
  Result := TDrawable.TryGetDrawableByGUID(AID, Drawable2D);
  if Result then
  begin
    ShowDiagram(Drawable2D.Control);
    Exit;
  end;
  Result := TDrawable3D.TryGetDrawableByGUID(AID, Drawable3D);
  if Result then
  begin
    ShowScene(Drawable3D.Control);
    Exit;
  end;
end;

class function TVisualization.SpaceCurve(ACtl: TVisCtl3D;
  AVisual: TVisual): TDrawable3D;
var
  Curve: TCurve3D absolute Result;
  CCurve: TColoredCurve3D absolute Result;
begin
  if AVisual.Data is TCurveDataR3c then
  begin
    CCurve := TColoredCurve3D.Create(ACtl);
    CCurve.Data := (AVisual.Data as TCurveDataR3c).Points;
  end
  else if AVisual.Data is TCurveDataR3 then
  begin
    Curve := TCurve3D.Create(ACtl);
    Curve.Data := (AVisual.Data as TCurveDataR3).Points;
  end
  else
    raise EClientVisualException.Create('Unknown space curve data class.');
end;

class function TVisualization.Object3D(ACtl: TVisCtl3D;
  AVisual: TVisual): TDrawable3D;
begin
  var D := AVisual.Data as TObject3DData;
  Result := D.ObjectClass.Create(ACtl);
  if not D.Name.IsEmpty then
    Result.Name := D.Name;
  if Result is TGeometricObject3D then
    TGeometricObject3D(Result).Color := clRed;
  if (Result is TObjModel) and not D.Data.IsEmpty then
    TObjModel(Result).LoadModel(D.Data);
end;

class function TVisualization.Surface(ACtl: TVisCtl3D;
  AVisual: TVisual): TDrawable3D;
var
  Surf: TCustomSurface absolute Result;
  CSurf: TCustomColoredSurface absolute Result;
begin
  if AVisual.Data is TColoredSurfaceData then
  begin
    CSurf := TCustomColoredSurface.Create(ACtl);
    CSurf.Data := (AVisual.Data as TColoredSurfaceData).Data;
    CSurf.Domain := (AVisual.Data as TColoredSurfaceData).Domain;
    CSurf.Nx := (AVisual.Data as TColoredSurfaceData).Nx;
    CSurf.Ny := (AVisual.Data as TColoredSurfaceData).Ny;
  end
  else if AVisual.Data is TSurfaceData then
  begin
    Surf := TCustomSurface.Create(ACtl);
    Surf.Data := (AVisual.Data as TSurfaceData).Data;
    Surf.Domain := (AVisual.Data as TSurfaceData).Domain;
    Surf.Nx := (AVisual.Data as TSurfaceData).Nx;
    Surf.Ny := (AVisual.Data as TSurfaceData).Ny;
    Surf.Color := clRed;
  end
  else
    raise EClientVisualException.Create('Unknown surface data class.');
end;

class function TVisualization.Text(ACtl: TVisCtl3D;
  AVisual: TVisual): TDrawable3D;
var
  Text: TTextRect absolute Result;
begin
  Text := TTextRect.Create(ACtl);
  Text.Text := (AVisual.Data as TTextData).Text;
end;

class function TVisualization.Text(ACtl: TVisCtl2D;
  AVisual: TVisual): TDrawable;
var
  Text: TText absolute Result;
begin
  Text := TText.Create(ACtl, ACtl.View);
  Text.Position := (AVisual.Data as TTextData).Position;
  Text.Text := (AVisual.Data as TTextData).Text;
end;

{ TManagedVisCtl2D }

class constructor TManagedVisCtl2D.ClassCreate;
begin
  FInstances := TDictionary<string, TManagedVisCtl2D>.Create;
end;

class destructor TManagedVisCtl2D.ClassDestroy;
begin
  if Assigned(FInstances) then
    for var Ctl in FInstances do
      Ctl.Value.Free;
  FreeAndNil(FInstances);
end;

constructor TManagedVisCtl2D.Create(AOwner: TComponent);
begin
  inherited;
  FDiagramName := TVisualization.Diagram;
  if Assigned(FInstances) then
    FInstances.Add(TVisualization.Diagram, Self);
end;

destructor TManagedVisCtl2D.Destroy;
begin
  if Assigned(FInstances) then
    FInstances.Remove(FDiagramName);
  inherited;
end;

{ TManagedVisCtl3D }

class constructor TManagedVisCtl3D.ClassCreate;
begin
  FInstances := TDictionary<string, TManagedVisCtl3D>.Create;
end;

class destructor TManagedVisCtl3D.ClassDestroy;
begin
  if Assigned(FInstances) then
    for var Ctl in FInstances do
      Ctl.Value.Free;
  FreeAndNil(FInstances);
end;

constructor TManagedVisCtl3D.Create(AOwner: TComponent);
begin
  inherited;
  FSceneName := TVisualization.Scene;
  if Assigned(FInstances) then
    FInstances.Add(TVisualization.Scene, Self);
end;

destructor TManagedVisCtl3D.Destroy;
begin
  if Assigned(FInstances) then
    FInstances.Remove(FSceneName);
  inherited;
end;

end.
