unit MainFm;
//---------------------------------------------------------------------------
// MainFm.pas                                           Modified: 29-Jan-2009
// Illumination - 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 - 2011,
// Yuriy Kotsarenko. All Rights Reserved.
//---------------------------------------------------------------------------
interface

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

//---------------------------------------------------------------------------
type
  TMainForm = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  private
    { Private declarations }
    GameTicks: Integer;
    MeshNo, Quality, NoTriangles: Integer;
    MeshName: string;

    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;

    procedure RecreateMesh();
    procedure CreateLights();

    procedure DrawPhong();
    procedure DrawBlinnPhong();
    procedure DrawMinneart();
    procedure DrawCookTorrance();
    procedure DrawIsotropicWard();
    procedure DrawOrenNayer();
  public
    { Public declarations }
  end;

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

//---------------------------------------------------------------------------
implementation
uses
 Vectors2, Vectors2px, Vectors3, AsphyreTypes, AsphyreUtils, AsphyreTimer,
 AsphyreFactory, AsphyreDb, AbstractDevices, AsphyreImages, AsphyreFonts,
 DX9Providers, GameTypes, AbstractCanvas, AsphyreMeshes, AsphyreScenes,
 AbstractRasterizer, AsphyreLights, AsphyreDirectionalLights,
 AsphyreTorusKnot, AsphyreSuperEllipsoid;
{$R *.dfm}

//---------------------------------------------------------------------------
procedure TMainForm.FormCreate(Sender: TObject);
begin
 // Enable BDS 2006+ debugger.
 ReportMemoryLeaksOnShutdown:= DebugHook <> 0;

 // This is the initial size of our rendering surface.
 DisplaySize:= Point2px(ClientWidth, ClientHeight);

 // In this example we use DirectX 9 because it allows to use multisampling,
 // which provides antialias to triangle edges.
 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;

 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;

 GameDevice.Multisamples:= 4;

 EventDeviceCreate.Subscribe(OnDeviceCreate, 0);

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

 // Create mesh with default parameters.
 MeshNo := 0;
 Quality:= 2;
 RecreateMesh();

 // Create all the lights we're going to use.
 CreateLights();

 // 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;

 FreeAndNil(GameFonts);
 FreeAndNil(GameImages);
 FreeAndNil(MediaASDb);
 FreeAndNil(GameScene);
 FreeAndNil(GameRaster);
 FreeAndNil(GameCanvas);
 FreeAndNil(GameDevice);
end;

//---------------------------------------------------------------------------
procedure TMainForm.OnDeviceCreate(Sender: TObject; Param: Pointer;
 var Handled: Boolean);
var
 Success: Boolean;
begin
 Success:= PBoolean(Param)^;

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

 GameImages.AddFromASDb('Candara.image', MediaASDb, '', False);
 GameImages.AddFromASDb('IceAge.image', MediaASDb, '', False);

 fontCandara:= GameFonts.Insert('/media.asdb | Candara.xml', 'Candara.image');
 fontIceAge:= GameFonts.Insert('/media.asdb | IceAge.xml', 'IceAge.image');

 Success:=
  Success and
  (fontCandara <> -1)and
  (fontIceAge <> -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;

//---------------------------------------------------------------------------
procedure TMainForm.RenderEvent(Sender: TObject);
var
 FpsText: string;
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:= DisplaySize.y / DisplaySize.x;

 NoTriangles:= 0;

 // Draw the model using different types of illumination.
 DrawPhong();
 DrawBlinnPhong();
 DrawMinneart();
 DrawCookTorrance();
 DrawIsotropicWard();
 DrawOrenNayer();

 // 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();

 // Display the information text.
 GameFonts[fontIceAge].Kerning:= -2.0;

 with GameFonts[fontIceAge] do
  begin
   FpsText:= 'FPS: ' + IntToStr(Timer.FrameRate);

   TextOut(
    Point2(DisplaySize.x - TextWidth(FpsText) - 4, DisplaySize.y - 24),
    FpsText, cColor2($FFFFE000, $FFFF0000));
  end;

 GameFonts[fontIceAge].TextOut(
  Point2(4.0, 4.0),
  'Quality Level: ' + IntToStr(Quality) + ', Triangles: ' + IntToStr(NoTriangles),
  cColor2($FFFFEC99, $FFF78900));

 GameFonts[fontIceAge].TextOut(
  Point2(4.0, 28.0),
  'Displayed Mesh: (' + IntToStr(MeshNo) + ') ' + MeshName,
  cColor2($FFFFE372, $FF17D94C));

 GameFonts[fontCandara].Kerning:= -2.0;
 GameFonts[fontCandara].TextOut(
  Point2(6.0, DisplaySize.y - 44),
  'Press Up or Down arrows to change the quality level.',
  cColor2($FFF1FFD5, $FF5CD900));

 GameFonts[fontCandara].TextOut(
  Point2(6.0, DisplaySize.y - 24),
  'Press Left or Right arrows to change the displayed mesh.',
  cColor2($FFF1FFD5, $FF5CD900));

 GameFonts[fontCandara].TextMid(
  Point2(DisplaySize.x * 0.2, DisplaySize.y * 0.2),
  'Phong', cColor2($FFE8F8C0, $FF00FF00));

 GameFonts[fontCandara].TextMid(
  Point2(DisplaySize.x * 0.5, DisplaySize.y * 0.2),
  'Blinn-Phong', cColor2($FFFFE88E, $FFFF0000));

 GameFonts[fontCandara].TextMid(
  Point2(DisplaySize.x * 0.8, DisplaySize.y * 0.2),
  'Minneart', cColor2($FFD3FFF3, $FF00B9B9));

 GameFonts[fontCandara].TextMid(
  Point2(DisplaySize.x * 0.2, DisplaySize.y * 0.81),
  'Cook-Torrance', cColor2($FFD8BCFF, $FF3B3BFF));

 GameFonts[fontCandara].TextMid(
  Point2(DisplaySize.x * 0.5, DisplaySize.y * 0.81),
  'Isotropic Ward', cColor2($FFFFCCE4, $FF4400DE));

 GameFonts[fontCandara].TextMid(
  Point2(DisplaySize.x * 0.8, DisplaySize.y * 0.81),
  'Oren-Nayer', cColor2($FFFFF1A5, $FF8AA800));


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

//---------------------------------------------------------------------------
procedure TMainForm.DrawPhong();
begin
 GameScene.Lights.ExcludeAll();
 GameScene.Lights.Insert(LightAmbient);
 GameScene.Lights.Insert(LightPhong);

 GameScene.BeginScene();

 WorldMtx.LoadIdentity();
 WorldMtx.Scale(100.0);
 WorldMtx.RotateX(GameTicks * 0.01);
 WorldMtx.RotateY(GameTicks * 0.0078);
 WorldMtx.RotateZ(GameTicks * 0.006581);

 GameScene.CullingType:= sctCounterClockwise;
 GameScene.Draw(Meshes[0], WorldMtx.RawMtx, nil, -1);

 Inc(NoTriangles, GameScene.TotalTriangles);

 GameScene.EndScene(ViewMtx.RawMtx);

 GameScene.Present(
  Point2(DisplaySize.x * 0.2, DisplaySize.y * 0.333),
  DisplaySize);
end;

//---------------------------------------------------------------------------
procedure TMainForm.DrawBlinnPhong();
begin
 GameScene.Lights.ExcludeAll();
 GameScene.Lights.Insert(LightAmbient);
 GameScene.Lights.Insert(LightBlinnPhong);

 GameScene.BeginScene();

 WorldMtx.LoadIdentity();
 WorldMtx.Scale(100.0);
 WorldMtx.RotateX(GameTicks * 0.00894);
 WorldMtx.RotateY(GameTicks * 0.009971);
 WorldMtx.RotateZ(GameTicks * 0.005432);

 GameScene.CullingType:= sctCounterClockwise;
 GameScene.Draw(Meshes[0], WorldMtx.RawMtx, nil, -1);

 Inc(NoTriangles, GameScene.TotalTriangles);

 GameScene.EndScene(ViewMtx.RawMtx);

 GameScene.Present(
  Point2(DisplaySize.x * 0.5, DisplaySize.y * 0.333),
  DisplaySize);
end;

//---------------------------------------------------------------------------
procedure TMainForm.DrawMinneart();
begin
 GameScene.Lights.ExcludeAll();
 GameScene.Lights.Insert(LightAmbient);
 GameScene.Lights.Insert(LightMinneart);

 GameScene.BeginScene();

 WorldMtx.LoadIdentity();
 WorldMtx.Scale(100.0);
 WorldMtx.RotateX(GameTicks * 0.00654321);
 WorldMtx.RotateY(GameTicks * 0.00765432);
 WorldMtx.RotateZ(GameTicks * 0.00876543);

 GameScene.CullingType:= sctCounterClockwise;
 GameScene.Draw(Meshes[0], WorldMtx.RawMtx, nil, -1);

 Inc(NoTriangles, GameScene.TotalTriangles);

 GameScene.EndScene(ViewMtx.RawMtx);

 GameScene.Present(
  Point2(DisplaySize.x * 0.8, DisplaySize.y * 0.333),
  DisplaySize);
end;

//---------------------------------------------------------------------------
procedure TMainForm.DrawCookTorrance();
begin
 GameScene.Lights.ExcludeAll();
 GameScene.Lights.Insert(LightAmbient);
 GameScene.Lights.Insert(LightCookTorrance);

 GameScene.BeginScene();

 WorldMtx.LoadIdentity();
 WorldMtx.Scale(100.0);
 WorldMtx.RotateX(GameTicks * 0.009731);
 WorldMtx.RotateY(GameTicks * 0.01);
 WorldMtx.RotateZ(GameTicks * 0.007891);

 GameScene.CullingType:= sctCounterClockwise;
 GameScene.Draw(Meshes[0], WorldMtx.RawMtx, nil, -1);

 Inc(NoTriangles, GameScene.TotalTriangles);

 GameScene.EndScene(ViewMtx.RawMtx);

 GameScene.Present(
  Point2(DisplaySize.x * 0.2, DisplaySize.y * 0.667),
  DisplaySize);
end;

//---------------------------------------------------------------------------
procedure TMainForm.DrawIsotropicWard();
begin
 GameScene.Lights.ExcludeAll();
 GameScene.Lights.Insert(LightAmbient);
 GameScene.Lights.Insert(LightIsotropicWard);

 GameScene.BeginScene();

 WorldMtx.LoadIdentity();
 WorldMtx.Scale(100.0);
 WorldMtx.RotateX(GameTicks * 0.007812);
 WorldMtx.RotateY(GameTicks * 0.009891);
 WorldMtx.RotateZ(GameTicks * 0.005443);

 GameScene.CullingType:= sctCounterClockwise;
 GameScene.Draw(Meshes[0], WorldMtx.RawMtx, nil, -1);

 Inc(NoTriangles, GameScene.TotalTriangles);

 GameScene.EndScene(ViewMtx.RawMtx);

 GameScene.Present(
  Point2(DisplaySize.x * 0.5, DisplaySize.y * 0.667),
  DisplaySize);
end;

//---------------------------------------------------------------------------
procedure TMainForm.DrawOrenNayer();
begin
 GameScene.Lights.ExcludeAll();
 GameScene.Lights.Insert(LightAmbient);
 GameScene.Lights.Insert(LightOrenNayer);

 GameScene.BeginScene();

 WorldMtx.LoadIdentity();
 WorldMtx.Scale(100.0);
 WorldMtx.RotateX(GameTicks * 0.004985);
 WorldMtx.RotateY(GameTicks * 0.008954);
 WorldMtx.RotateZ(GameTicks * 0.01);

 GameScene.CullingType:= sctCounterClockwise;
 GameScene.Draw(Meshes[0], WorldMtx.RawMtx, nil, -1);

 Inc(NoTriangles, GameScene.TotalTriangles);

 GameScene.EndScene(ViewMtx.RawMtx);

 GameScene.Present(
  Point2(DisplaySize.x * 0.8, DisplaySize.y * 0.667),
  DisplaySize);
end;

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

//---------------------------------------------------------------------------
procedure TMainForm.RecreateMesh();
var
 Mesh: TAsphyreMesh;
begin
 Meshes.RemoveAll();

 Mesh:= TAsphyreMesh.Create();

 case MeshNo of
  0: // 2-1 Torus Knot
   begin
    CreateTorusKnot(Mesh, 0.25, 0.3 * 0.25, 2, 1,
     32 + 32 * Quality, 8 + 4 * Quality, 1.0, 1.0);
    MeshName:= '2-1 Torus Knot';
   end;

  1: // 1-2 Torus Knot
   begin
    CreateTorusKnot(Mesh, 0.25, 0.3 * 0.25, 1, 2,
     32 + 32 * Quality, 8 + 4 * Quality, 1.0, 1.0);
    MeshName:= '1-2 Torus Knot';
   end;

  2: // 3-2 Torus Knot
   begin
    CreateTorusKnot(Mesh, 0.25, 0.3 * 0.25, 3, 2,
     32 + 32 * Quality, 8 + 4 * Quality, 1.0, 1.0);
    MeshName:= '3-2 Torus Knot';
   end;

  3: // Sphere
   begin
    CreateSuperEllipsoid(Mesh, 16 + 20 * Quality, 1.0, 1.0);
    MeshName:= 'Sphere';
   end;

  4: // Super Ellipse
   begin
    CreateSuperEllipsoid(Mesh, 16 + 16 * Quality, 4.0, 4.0);
    MeshName:= 'Super Ellipse';
   end;

  5: // Round Cube
   begin
    CreateSuperEllipsoid(Mesh, 8 + 8 * Quality, 0.25, 0.25);
    Mesh.Rescale(Vector3(0.75, 0.75, 0.75));
    MeshName:= 'Round Cube';
   end;

  6: // 5-4 Torus Knot
   begin
    CreateTorusKnot(Mesh, 0.3, 0.2 * 0.3, 5, 4,
     32 + 32 * Quality, 8 + 4 * Quality, 1.0, 1.0);
    MeshName:= '5-4 Torus Knot';
   end;

  7: // 7-8 Torus Knot
   begin
    CreateTorusKnot(Mesh, 0.3, 0.2 * 0.3, 7, 8,
     48 + 48 * Quality, 10 + 6 * Quality, 1.0, 1.0);
    MeshName:= '7-8 Torus Knot';
   end;
 end;

 Meshes.Include(Mesh);
end;

//---------------------------------------------------------------------------
procedure TMainForm.CreateLights();
begin
 LightAmbient:= TAsphyreAmbientLight.Create();
 LightAmbient.Color:= $050505;

 LightPhong:= TDirectionalPhongLight.Create();
 LightPhong.Direction:= Norm3(Vector3(1.0, -4.0, -1.0));
 LightPhong.Diffuse:= $00FF00;

 LightBlinnPhong:= TDirectionalBlinnPhongLight.Create();
 LightBlinnPhong.Direction:= Norm3(Vector3(1.0, -4.0, -1.0));
 LightBlinnPhong.Diffuse:= $FF0000;
 LightBlinnPhong.Power  := 24.0;

 LightMinneart:= TDirectionalMinneartLight.Create();
 LightMinneart.Direction:= Norm3(Vector3(1.0, -1.0, -2.0));
 LightMinneart.Color    := $00FFFF;

 LightCookTorrance:= TDirectionalCookTorranceLight.Create();
 LightCookTorrance.Direction:= Norm3(Vector3(1.0, -4.0, -1.0));
 LightCookTorrance.Diffuse  := $00000FF;
 LightCookTorrance.SpecPower:= 1.0;

 LightIsotropicWard:= TDirectionalIsotropicWardLight.Create();
 LightIsotropicWard.Direction:= Norm3(Vector3(1.0, -4.0, -1.0));
 LightIsotropicWard.Diffuse  := $FF000FF;

 LightOrenNayer:= TDirectionalOrenNayerLight.Create();
 LightOrenNayer.Direction:= Norm3(Vector3(1.0, -1.0, -2.0));
 LightOrenNayer.Diffuse  := $FFFF00;
end;

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

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

//---------------------------------------------------------------------------
procedure TMainForm.FormKeyDown(Sender: TObject; var Key: Word;
 Shift: TShiftState);
begin
 if (Key = VK_UP) then
  begin
   Quality:= Min2(Quality + 1, 8);
   RecreateMesh();
  end;

 if (Key = VK_DOWN) then
  begin
   Quality:= Max2(Quality - 1, 0);
   RecreateMesh();
  end;

 if (Key = VK_LEFT) then
  begin
   MeshNo:= MeshNo - 1;
   if (MeshNo < 0) then MeshNo:= 7;

   RecreateMesh();
  end;

 if (Key = VK_RIGHT) then
  begin
   MeshNo:= MeshNo + 1;
   if (MeshNo > 7) then MeshNo:= 0;

   RecreateMesh();
  end;
end;

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