unit MainFm;
//---------------------------------------------------------------------------
// MainFm.pas                                           Modified: 29-Jan-2009
// Granny Knot - Asphyre Sphinx example                           Version 1.0
//---------------------------------------------------------------------------
// This example is based on Granny Knot definition described by Paul Bourke
// on his web site: http://local.wasp.uwa.edu.au/~pbourke/
//
// The code renders the animated knot using billboards and rotates the shape.
//---------------------------------------------------------------------------
// Important Notice:
//
// If you modify/use this code or one of its parts either in original or
// modified form, you must comply with Mozilla Public License v1.1,
// specifically section 3, "Distribution Obligations". Failure to do so will
// result in the license breach, which will be resolved in the court.
// Remember that violating author's rights is considered a serious crime in
// many countries. Thank you!
//
// !! Please *read* Mozilla Public License 1.1 document located at:
//  http://www.mozilla.org/MPL/
//---------------------------------------------------------------------------
// The contents of this file are subject to the Mozilla Public License
// Version 1.1 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
// http://www.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// License for the specific language governing rights and limitations
// under the License.
//
// The Original Code is MainFm.pas.
//
// The Initial Developer of the Original Code is Yuriy Kotsarenko.
// Portions created by Yuriy Kotsarenko are Copyright (C) 2000 - 2009,
// Yuriy Kotsarenko. All Rights Reserved.
//---------------------------------------------------------------------------
{$mode objfpc}{$H+}

//---------------------------------------------------------------------------
interface

//---------------------------------------------------------------------------
uses
  Messages, SysUtils, LResources, Classes, Controls, Forms, Dialogs;

//---------------------------------------------------------------------------
type
  TMainForm = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormResize(Sender: TObject);
  private
    { Private declarations }
    GameTicks: Integer;

    procedure OnDeviceCreate(Sender: TObject; Param: Pointer;
     var Handled: Boolean);

    procedure TimerEvent(Sender: TObject);
    procedure ProcessEvent(Sender: TObject);
    procedure RenderEvent(Sender: TObject);

    procedure WMDisplayChange(var message:TMessage); message WM_DISPLAYCHANGE;

    function Sine2xTheta(Theta: Single): Single;
    procedure DrawGrannyKnot();
  public
    { Public declarations }
  end;

//---------------------------------------------------------------------------
var
  MainForm: TMainForm;

//---------------------------------------------------------------------------
implementation
uses
 Vectors2, Vectors2px, Vectors3, Matrices4, AsphyreTimer, AsphyreFactory,
 AsphyreTypes, AsphyreDb, AbstractDevices, AsphyreImages, AsphyreFonts,
 DX9Providers, GameTypes, AbstractCanvas, AsphyreScenes, AbstractRasterizer,
 AsphyreBillboards;

//---------------------------------------------------------------------------
{$include GrannyPalette.inc}

//---------------------------------------------------------------------------
procedure TMainForm.FormCreate(Sender: TObject);
begin
 // Set the display size
 DisplaySize:= Point2px(ClientWidth, ClientHeight);

 // Indicate that we're using DirectX 9
 Factory.UseProvider(idDirectX9);

 // Create Asphyre components in run-time.
 GameDevice:= Factory.CreateDevice();
 GameCanvas:= Factory.CreateCanvas();
 GameRaster:= Factory.CreateRasterizer();
 GameImages:= TAsphyreImages.Create();

 GameFonts:= TAsphyreFonts.Create();
 GameFonts.Images:= GameImages;
 GameFonts.Canvas:= GameCanvas;

 GameScene:= TAsphyreScene.Create();
 GameScene.Raster:= GameRaster;
 GameScene.DisplaySize:= DisplaySize;

 Billboards:= TAsphyreBillboards.Create();

 MediaASDb:= TASDb.Create();
 MediaASDb.FileName:= ExtractFilePath(ParamStr(0)) + 'media.asdb';
 MediaASDb.OpenMode:= opReadOnly;

 GameDevice.WindowHandle:= Self.Handle;
 GameDevice.Size    := DisplaySize;
 GameDevice.Windowed:= True;
 GameDevice.VSync   := False;

 EventDeviceCreate.Subscribe(@OnDeviceCreate, 0);

 // Attempt to initialize Asphyre device.
 if (not GameDevice.Initialize()) then
  begin
   ShowMessage('Failed to initialize Asphyre device.');
   Application.Terminate();
   Exit;
  end;

 // Create rendering timer.
 Timer.OnTimer  := @TimerEvent;
 Timer.OnProcess:= @ProcessEvent;
 Timer.Speed    := 60.0;
 Timer.MaxFPS   := 4000;
 Timer.Enabled  := True;
end;

//---------------------------------------------------------------------------
procedure TMainForm.FormDestroy(Sender: TObject);
begin
 Timer.Enabled:= False;

 // Release all Asphyre components.
 FreeAndNil(GameFonts);
 FreeAndNil(GameImages);
 FreeAndNil(MediaASDb);
 FreeAndNil(Billboards);
 FreeAndNil(GameScene);
 FreeAndNil(GameRaster);
 FreeAndNil(GameCanvas);
 FreeAndNil(GameDevice);
end;

//---------------------------------------------------------------------------
procedure TMainForm.OnDeviceCreate(Sender: TObject; Param: Pointer;
 var Handled: Boolean);
var
 Success: Boolean;
begin
 // This variable returns "Success" to Device initialization, so if you
 // set it to False, device creation will fail.
 Success:= PBoolean(Param)^;

 GameImages.RemoveAll();
 GameFonts.RemoveAll();

 // This image is used by our bitmap font.
 GameImages.AddFromASDb('tahoma9b.image', MediaASDb, '', False);

 fontTahoma9b:= GameFonts.Insert('/media.asdb | tahoma9b.xml',
  'tahoma9b.image');

 imageParticles:=
  GameImages.AddFromASDb('particles.image', MediaASDb, '', True);

 Success:=
  Success and
  (imageParticles <> -1)and
  (fontTahoma9b <> -1);

 PBoolean(Param)^:= Success;
end;

//---------------------------------------------------------------------------
procedure TMainForm.TimerEvent(Sender: TObject);
begin
 GameDevice.Render(@RenderEvent, $000000);
 Timer.Process();
end;

//---------------------------------------------------------------------------
procedure TMainForm.ProcessEvent(Sender: TObject);
begin
 Inc(GameTicks);
end;

//---------------------------------------------------------------------------
function TMainForm.Sine2xTheta(Theta: Single): Single;
begin
 Result:= (Sin(Theta * Pi * 2.0 - Pi * 0.5) + 1.0) * 0.5;
end;

//---------------------------------------------------------------------------
procedure TMainForm.DrawGrannyKnot();
const
 Segments = 2048;
 Scale    = 1.0;
var
 Pos: TVector3;
 Phi, vSize, Omega, Theta: Single;
 KnotColor: Cardinal;
 i: Integer;
begin
 for i:= 0 to Segments - 1 do
  begin
   Phi:= (i * 2.0 * Pi / Segments) + (GameTicks * 0.007);
   Phi:= Phi - Trunc(Phi / (2.0 * Pi)) * 2.0 * Pi;

   Pos.x:= -22.0 * Cos(Phi) - 128.0 * Sin(Phi) - 44.0 * Cos(3.0 * Phi) -
    78.0 * Sin(3.0 * Phi);
   Pos.y:= -10.0 * Cos(2.0 * Phi) - 27.0 * Sin(2.0 * Phi) +
    38.0 * Cos(4.0 * Phi) + 46.0 * Sin(4.0 * Phi);
   Pos.z:= 70.0 * Cos(3.0 * Phi) - 40.0 * Sin(3.0 * Phi);

   Pos:= Pos * Scale;

   WorldMtx.LoadIdentity();
   WorldMtx.RotateY(Sine2xTheta(GameTicks * 0.0007) * 4.0 * Pi);
   WorldMtx.RotateX(-Sine2xTheta(GameTicks * 0.001) * 3.0 * Pi);
   WorldMtx.RotateZ(Sine2xTheta(GameTicks * 0.0011) * 2.0 * Pi);

   Pos:= Pos * WorldMtx.RawMtx^;

   Theta:= i / Segments;
   vSize:= Sine2xTheta(Theta * 2.0 + GameTicks * 0.011) * 0.5 + 0.5;
   Omega:= Sine2xTheta(Theta * 4.0 - GameTicks * 0.0027) * 2.0 * Pi;

   Theta:= Sine2xTheta(Theta - GameTicks * 0.0013);
   KnotColor:= GrannyPalette[Round(Theta * 2047.0)];

   Billboards.UseImagePt(GameImages[imageParticles], 6);
   Billboards.TexMap(Pos, Point2(16.0, 16.0) * vSize, Omega, KnotColor);
  end;
end;

//---------------------------------------------------------------------------
procedure TMainForm.RenderEvent(Sender: TObject);
begin
 // The following call is not exactly necessary (since it's called
 // automatically), unless you have drawn 2D stuff before that.
 GameRaster.ResetStates();

 // Configure the view matrix and aspect ratio of our 3D-to-2D projection.
 ViewMtx.LoadIdentity();
 ViewMtx.LookAt(Vector3(0.0, 25.0, -200.0), ZeroVec3, AxisYVec3);

 GameScene.AspectRatio:= GameDevice.Size.y / GameDevice.Size.x;

 // Start rendering the 3D scene.
 GameScene.BeginScene();

 // The know is drawn using several thousand billboards.
 DrawGrannyKnot();

 // In this call the culling and illumination stage occurs.
 GameScene.EndScene(ViewMtx.RawMtx);

 // The following call must be located *exactly* between EndScene and Present.
 Billboards.Render(GameScene, ViewMtx.RawMtx);

 // This call presents the entire scene on the screen.
 GameScene.Present(
  Point2(DisplaySize.x * 0.5, DisplaySize.y * 0.5),
  DisplaySize, reAdd);

 // The raster class is used to draw triangles on the screen from the 3D scene.
 // Make sure to flush its buffers before drawing 2D stuff.
 GameRaster.Flush();

 // In order to continue drawing 2D stuff, we need to make this call so that
 // the canvas is ready.
 GameCanvas.ResetStates();

 GameFonts[fontTahoma9b].TextOut(
  Point2(4.0, 4.0),
  'FPS: ' + IntToStr(Timer.FrameRate),
  cColor2($FFFFE887, $FFFF0000), 1.0);

 // The following call is not necessary here, unless you want to render 3D
 // stuff afterwards.
 GameCanvas.Flush();
end;

//---------------------------------------------------------------------------
procedure TMainForm.WMDisplayChange(var message: TMessage);
begin
 if (GameDevice <> nil)and(GameDevice.Active)and(GameDevice.Windowed) then
  GameDevice.Reset();
end;

//---------------------------------------------------------------------------
procedure TMainForm.FormResize(Sender: TObject);
begin
 DisplaySize:= Point2px(ClientWidth, ClientHeight);

 if (GameDevice.Active) then
  GameDevice.Size:= DisplaySize;
end;


//---------------------------------------------------------------------------
initialization
  {$I MainFm.lrs}

//---------------------------------------------------------------------------
end.
