unit sgGLOrbit3D;

interface

uses
  Windows, SysUtils, Classes, Forms, Controls, Graphics, Messages,
  DXFImage, sgConsts, sgImage, sgOpenGL;

type
  TsgTwoPoints = array [0..1] of TPoint;
  Tsg3DStates = (stateX, stateY, stateZ, stateXY);

  TsgOrbit3D = class(TGraphicControl)
  private
    { Private declarations }
    FBigRadius: Integer;
    FDrawing: TsgImage;
    FCADImage: TsgDXFImage;
    FCurrentState: Tsg3DStates;
    FIsMouseDown: Boolean;
    FMouseCoords: TPoint;
    FOpenGLDC: cardinal;
    FPtCenter: TPoint;
    FPtHoriz: TsgTwoPoints;
    FPtVert: TsgTwoPoints;
    FSmallRadius: Integer;
    procedure SetCADImage(const Value: TsgDXFImage);
    procedure SetCursorState(const X, Y: Integer);
    procedure SetPtCenter(const Value: TPoint);
    function GetPtCenter: TPoint;
    procedure SelectRotate(Axis: TsgAxes; Angle: Extended);
    procedure SetOpenGLDC(ADC: Cardinal);
  protected
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure SetParent(AParent: TWinControl); override;
    procedure SetDrawing(ADrawing: TsgImage);
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Paint; override;
    property PtCenter: TPoint read GetPtCenter write SetPtCenter;
  published
    { Published declarations }
    property BigRadius: Integer read FBigRadius;
    property Drawing: TsgImage read FDrawing write SetDrawing;
    property CADImage: TsgDXFImage read FCADImage write SetCADImage;
    property OpenGLDC: Cardinal read FOpenGLDC write SetOpenGLDC;
    property SmallRadius: Integer read FSmallRadius;
  end;

//procedure Register;
var
  pglqo: pointer;

implementation
{.$R *.RES}

{
procedure Register;
begin
  RegisterComponents('Samples', [TsgOrbit3D]);
end;
}
{ TsgOrbit3D }

constructor TsgOrbit3D.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  //Color := TColor(RGB(68, 235, 140));
  Color := clNone;
  PtCenter := Point(0, 0);
  FBigRadius := 100;
  FDrawing := nil;
  FSmallRadius:= 10;
  FIsMouseDown := False;
  FOpenGLDC := 0;
  //Self.ParentBackground := True;
  //pglqo := gluNewQuadric();
  //Application.OnIdle := Idle;
end;

procedure TsgOrbit3D.SetPtCenter(const Value: TPoint);
begin
  FPtCenter := Value;
  FPtHoriz[0].X := FPtCenter.X;
  FPtHoriz[0].Y := FPtCenter.Y - FBigRadius;
  FPtHoriz[1].X := FPtCenter.X;
  FPtHoriz[1].Y := FPtCenter.Y + FBigRadius;
  FPtVert[0].X := FPtCenter.X - FBigRadius;
  FPtVert[0].Y := FPtCenter.Y;
  FPtVert[1].X := FPtCenter.X + FBigRadius;
  FPtVert[1].Y := FPtCenter.Y;
end;

procedure TsgOrbit3D.MouseDown(Button: TMouseButton; Shift: TShiftState; X,
  Y: Integer);
begin
  inherited MouseDown(Button, Shift, X, Y);
  if (Button = mbLeft) and ((FCADImage <> nil)or(FOpenGLDC <> 0)) then
  begin
    FIsMouseDown := True;
    FMouseCoords := Point(X, Y);
    SetCursorState(X, Y);
  end;
end;

procedure TsgOrbit3D.MouseMove(Shift: TShiftState; X, Y: Integer);
var
  dX, dY: Integer;
  //vScale: Double;

begin
  inherited MouseMove(Shift, X, Y);
  if FIsMouseDown then
  begin
    dX := X - FMouseCoords.X;
    dY := Y - FMouseCoords.Y;
    case FCurrentState of
      stateX:
        SelectRotate(axisX, dY);
      stateY:
        SelectRotate(axisY, dX);
      stateZ:
        begin
          if Abs(dX) > Abs(dY) then
          begin
            if FMouseCoords.Y < PtCenter.Y then
              SelectRotate(axisZ, -dX)
            else
              SelectRotate(axisZ, dX)
          end
          else
          begin
            if FMouseCoords.X < PtCenter.X then
              SelectRotate(axisZ, dY)
            else
              SelectRotate(axisZ, -dY)
          end;
        end;
      stateXY:
        begin
          SelectRotate(axisX, dY);
          SelectRotate(axisY, dX);
        end;
    end;
    FMouseCoords := Point(X, Y);
  end
  else
    SetCursorState(X, Y);
end;

procedure TsgOrbit3D.MouseUp(Button: TMouseButton; Shift: TShiftState; X,
  Y: Integer);
var vScale: Double;
begin
  inherited MouseUp(Button, Shift, X, Y);
  if (Button = mbLeft) and ((FCADImage <> nil)or(FOpenGLDC <> 0)) then
  begin
    FIsMouseDown := False;
    // #poltp changed begin
    if (FDrawing <> nil)and(FOpenGLDC = 0) then
    begin
      vScale := FDrawing.Scale;
      Visible := False;
      CADImage.GetExtents;
      FDrawing.Width := CADImage.Width;
      FDrawing.Height := CADImage.Height;
      FDrawing.ChangeScale(True, vScale, Point(0, 0));
      Visible := True;
      //FDrawing.Invalidate;
    end;
    // #poltp changed end
  end;
  SetCursorState(X, Y);
end;

procedure TsgOrbit3D.SetCADImage(const Value: TsgDXFImage);
begin
  FCADImage := Value;
  if FCADImage <> nil then
    FOpenGLDC := 0;
end;

procedure TsgOrbit3D.Paint;
begin
  inherited Paint;
  if Parent = nil then
    Exit;
  if Align <> alNone then
    Align := alNone;
  Top := 0;
  Left := 0;
  Width := Parent.ClientWidth;
  Height := Parent.ClientHeight;
  FBigRadius := Width shr 1;
  if FBigRadius > (Height shr 1) then
    FBigRadius := Height shr 1;
  FBigRadius := FBigRadius - (FBigRadius shr 2);
  PtCenter := Point(Width shr 1, Height shr 1);

  if Canvas.Pen.Color <> Color then
    Canvas.Pen.Color := TColor(RGB(68, 235, 140));

  if Canvas.Brush.Style <> bsClear then
    Canvas.Brush.Style := bsClear;

  //gluDisk(pglqo, FBigRadius, FBigRadius + 1, 20, 10);
  // Big circle
  Canvas.Ellipse(FPtCenter.X - FBigRadius, FPtCenter.Y - FBigRadius, FPtCenter.X + FBigRadius, FPtCenter.Y + FBigRadius);
  // Horizontal-turn circles (on display in vertical line)
  Canvas.Ellipse(FPtHoriz[0].X - FSmallRadius, FPtHoriz[0].Y - FSmallRadius, FPtHoriz[0].X + FSmallRadius, FPtHoriz[0].Y + FSmallRadius);
  Canvas.Ellipse(FPtHoriz[1].X - FSmallRadius, FPtHoriz[1].Y - FSmallRadius, FPtHoriz[1].X + FSmallRadius, FPtHoriz[1].Y + FSmallRadius);
  // Vertical-turn circles (on display in horizontal line)
  Canvas.Ellipse(FPtVert[0].X - FSmallRadius, FPtVert[0].Y - FSmallRadius, FPtVert[0].X + FSmallRadius, FPtVert[0].Y + FSmallRadius);
  Canvas.Ellipse(FPtVert[1].X - FSmallRadius, FPtVert[1].Y - FSmallRadius, FPtVert[1].X + FSmallRadius, FPtVert[1].Y + FSmallRadius);
end;

procedure TsgOrbit3D.SetCursorState(const X, Y: Integer);
  function InsideOf(CPt: TPoint; Radius: Integer): Boolean;
  var
    Vect: TPoint;
  begin
    Result := False;
    Vect := Point(X - CPt.X, Y - CPt.Y);
    if (Sqrt(Sqr(Vect.X) + Sqr(Vect.Y)) <= Radius) then
      Result := True;
  end;
var
  St: Tsg3DStates;
begin
  St := stateZ;
  if InsideOf(FPtCenter, FBigRadius) then
    St := stateXY;
  if InsideOf(FPtHoriz[0], FSmallRadius) or InsideOf(FPtHoriz[1], FSmallRadius) then
    St := stateX
  else
    if InsideOf(FPtVert[0], FSmallRadius) or InsideOf(FPtVert[1], FSmallRadius) then
      St := stateY;
  FCurrentState := St;
  Cursor := TCursor(Ord(St) + 1);
  Parent.Perform(WM_SETCURSOR, Parent.Handle, HTCLIENT);
  //Windows.SetClassLong(Application.Handle, GCL_HCURSOR, HCURSOR(Cursor));
end;

procedure TsgOrbit3D.SetParent(AParent: TWinControl);
begin
  inherited SetParent(AParent);
  if Parent <> nil then
  begin
    Top := 0;
    Left := 0;
    Width := Parent.ClientWidth;
    Height := Parent.ClientHeight;
  end;
end;

destructor TsgOrbit3D.Destroy;
begin
  Cursor := crDefault;
  inherited Destroy;
end;

procedure TsgOrbit3D.SetDrawing(ADrawing: TsgImage);
begin
  FDrawing := ADrawing;
end;

function TsgOrbit3D.GetPtCenter: TPoint;
begin
  Result := FPtCenter;
end;

procedure TsgOrbit3D.SelectRotate(Axis: TsgAxes; Angle: Extended);
begin
  if FOpenGLDC = 0 then
  begin
    if CADImage <> nil then
      CADImage.Rotate(Axis, Angle);
  end
  else
    RotateGL(Axis, Angle);
end;

procedure TsgOrbit3D.SetOpenGLDC(ADC: Cardinal);
begin
  FOpenGLDC := ADC;
end;

initialization
  Screen.Cursors[Ord(stateX) + 1] := Windows.LoadCursor(HInstance, 'X');
  Screen.Cursors[Ord(stateY) + 1] := Windows.LoadCursor(HInstance, 'Y');
  Screen.Cursors[Ord(stateZ) + 1] := Windows.LoadCursor(HInstance, 'Z');
  Screen.Cursors[Ord(stateXY)+ 1] := Windows.LoadCursor(HInstance, 'XY');

end.
