(*
    DEMO06 - Magnifying glass
    (c) 1994 Alessandro Scotti
*)
uses Crt, Modex;

const
  R = 40;               (* Lens radius *)
  K : real = 1.8;       (* Magnifying factor, less makes a stronger lens *)
type
  TLine   = array[ 0..319 ] of byte;
  PLine   = ^TLine;
  TScreen = array[ 0..239 ] of PLine;
var
  VScreen: TScreen;                             (* Virtual screen *)
  BallX  : array[ 0..R, 0..R ] of integer;
  BallY  : array[ 0..R, 0..R ] of integer;
  Sprite : array[ -R..R, -R..R ] of byte;
  Page   : word;

(* Returns "lens-view" coordinates of X,Y *)
procedure GetCoords( var X, Y: integer );
var
  LR, Z, SinA, SinB, TgB, Q: real;
begin
  LR := Sqrt( X*X + Y*Y );
  if( LR = 0 ) then Exit;
  if( LR < R ) then begin
    Z := Sqrt( R*R - LR*LR );
    SinA := LR / R;
    SinB := SinA / K;
    TgB := SinB / Sqrt( 1-SinB*SinB );
    Q := LR - TgB*Z;
    X := Round( X * ( Q/LR ) );
    Y := Round( Y * ( Q/LR ) );
  end;
end;

procedure Init;
var
  F      : file;
  Palette: array[ 0..767 ] of record R, G, B: byte; end;
  X, Y,
  X2, Y2 : integer;
begin
  (* Load background image *)
  Assign( F, 'demo06.dat' );
  Reset( F, 1 );
  BlockRead( F, Palette, 768 );
  mxSetPalette( @Palette, 0, 256 );
  for Y:=0 to 239 do begin
    New( VScreen[Y] );
    BlockRead( F, VScreen[Y]^, 320 );
    mxPutImage( VScreen[Y], 0, 480+Y, 320, 1, OP_MOVE );
  end;
  Close( F );
  (* Build lens *)
  for X:=0 to R do begin
    for Y:=0 to R do begin
      X2 := X;
      Y2 := Y;
      GetCoords( X2, Y2 );
      BallX[X, Y] := X2;
      BallY[X, Y] := Y2;
    end;
  end;
end;

procedure PutLens( OX, OY: integer );
var
  X, Y: integer;
begin
  for X:=0 to R do begin
    for Y:=0 to R do begin
      Sprite[Y][X] := VScreen[ OY+BallY[X,Y] ]^[ OX+BallX[X,Y] ];
      Sprite[Y][-X] := VScreen[ OY+BallY[X,Y] ]^[ OX-BallX[X,Y] ];
      Sprite[-Y][X] := VScreen[ OY-BallY[X,Y] ]^[ OX+BallX[X,Y] ];
      Sprite[-Y][-X] := VScreen[ OY-BallY[X,Y] ]^[ OX-BallX[X,Y] ];
    end;
  end;
  (* Draw the sprite *)
  mxPutImage( @Sprite, OX-R, OY-R+Page, 2*R+1, 2*R+1, OP_MOVE );
end;

function Delta: integer;
begin
  Delta := Random(3)+2;
end;

procedure Check( Cond: boolean; var Coord, DeltaC: integer; NewCoord, Sign: integer );
begin
  if( Cond ) then begin
    Coord := NewCoord;
    DeltaC := Sign*Delta;
  end;
end;

var
  X, Y, DX, DY: integer;
  C: char;
begin
  mxInit;
  mxSetMode( MX_320x240 );
  Init;
  Page := 240;
  X := R;
  Y := R;
  Randomize;
  DX := Delta;
  DY := Delta;

  (* Main loop *)
  repeat
    (* Update video *)
    mxBitBlt( 0, 480, 320, 240, 0, Page );
    PutLens( X, Y );
    mxCircle( X, Page+Y, R, 0 );
    (* Update lens coordinates *)
    Inc( X, DX );
    Check( X+R >= 319, X, DX, 319-R, -1 );
    Check( X <= R, X, DX, R, +1 );
    Inc( Y, DY );
    Check( Y+R >= 239, Y, DY, 239-R, -1 );
    Check( Y <= R, Y, DY, R, +1 );
    (* Flip pages: double buffering, avoid wait for display *)
    case Page of
      0  : begin PortW[$3D4] := $000C; Page := 240; end;
      240: begin PortW[$3D4] := $4B0C; Page := 0; end;
    end;
    mxWaitRetrace; (* Wait for hidden page to show *)
  until( KeyPressed );

  mxSetMode( MX_TEXT );
  mxTerm;
end.
