unit MainFm;
(*
 * Copyright (c) 2015 - 2025 Dr. Yuriy Kotsarenko. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations under the License.
 *)

// This example illustrates the following features:
//  * Accurate Order-Independent Transparency (OIT) with Frosted Glass effect.
//  * Object selection, which uses object's bounding boxes and accompanying voxel representation to
//    determine accurately what object is being pointed at, including approximate 3D location that mouse
//    is pointing on the object's surface.
//  * Hierarchial object structure, where some objects are parented to other objects.
//  * Grapher module for visualizing object's bounding box.

interface

uses
{$IFDEF MSWINDOWS}
  PXT.Controls,
{$ELSE}
  OpenGLContext,
{$ENDIF}
  Graphics, Classes, Forms, Controls, Dialogs, PXT.Types, PXT.Graphics;

type
  TMainForm = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    FRenderingControl: TWinControl;
    FDisplaySize: TPoint2i;
    FDisplayScale: Single;
    FMousePos: TPoint2i;

    FDevice: TDevice;
    FSwapChain: TSwapChain;
    FCanvas: TCanvas;
    FTextRenderer: TTextRenderer;
    FGrapher: TGrapher;
    FCamera: TActorCamera;

    FMeshes: TSceneMeshes;
    FObjects: TObjectModels;
    FSceneLights: TSceneLights;
    FMaterials: TObjectMaterials;

    FTextureCabinet: TTextureCabinet;
    FShadowCastingAtlas: TShadowCastingAtlas;
    FShadowCasterView: TObjectModelView;

    FSceneDepthNormals: TScene;
    FSceneModeling: TScene;

    FViewport: TObjectModelView;
    FProjection: TMatrix4f;

    FMouseRay: TRay;
    FSelected: TObjectModel;
    FShadowMapNeedsUpdate: Boolean;

    procedure RenderingControlResize(Sender: TObject);
    procedure RenderingControlMouseDown(Sender: TObject; Button: Controls.TMouseButton; Shift: TShiftState;
      X, Y: Integer);
    procedure RenderingControlMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
    procedure RenderingControlMouseUp(Sender: TObject; Button: Controls.TMouseButton; Shift: TShiftState;
      X, Y: Integer);
    procedure RenderingControlMouseWheelUp(Sender: TObject; Shift: TShiftState; MousePos: TPoint;
      var Handled: Boolean);
    procedure RenderingControlMouseWheelDown(Sender: TObject; Shift: TShiftState; MousePos: TPoint;
      var Handled: Boolean);
    procedure RenderingControlPaint(Sender: TObject);
    procedure RenderObjectsOpaque(const AScene: TScene; const AView: TObjectModelView);
    procedure RenderObjectsGlassy(const AScene: TScene; const AView: TObjectModelView);
    procedure CreateObjects;
    procedure CreateAndLoadMeshes;
    procedure CreateLightsAndMaterials;
    procedure CreateRenderingControl;
  public
  end;

var
  MainForm: TMainForm;

implementation
{$R *.lfm}

uses
  LCLType, SysUtils;

const
  MeshesBasePath = '../../../../Media/Office/';

  VertexElements: array[0..2] of TVertexElement = (
    (Name: 'vertexPosition'; Format: TElementFormat.Float; Count: 3; Channel: 0; Offset: 0),
    (Name: 'vertexNormal'; Format: TElementFormat.Float; Count: 3; Channel: 0; Offset: 0),
    (Name: 'vertexColor'; Format: TElementFormat.Byte; Count: 4; Channel: 0 or TVertexElement.Normalized; Offset: 0));

  LightPosition: TVector3f = (X: 300.0; Y: 800.0; Z: -300.0);

procedure TMainForm.FormCreate(Sender: TObject);
var
  LDepthClipNegative: Boolean;
  LAmbientOcclusion: TAmbientOcclusionParameters;
begin
  CreateRenderingControl;
  FDisplaySize := Point2i(FRenderingControl.Width, FRenderingControl.Height);
  FDisplayScale := PixelsPerInch / 96.0;
  LDepthClipNegative := FDevice.Behavior and TDeviceBehavior.DepthClipNegative <> 0;
  try
  {$IFDEF MSWINDOWS}
    FDevice := TDevice.Create;
  {$ELSE}
    // On Linux, using TOpenGLContext control from LCL.
    FDevice := TDevice.CreateWrapped;
  {$ENDIF}

    FSwapChain := TSwapChain.Create(FDevice,
      {$IFDEF MSWINDOWS}TRenderingControl(FRenderingControl).WindowHandle{$ELSE}0{$ENDIF},
      FDisplaySize);

    FCanvas := TCanvas.Create(FDevice);
    FTextRenderer := TTextRenderer.Create(FCanvas, Point2i(256, 256), TPixelFormat.LA8);
    FGrapher := TGrapher.Create(FDevice);
    FMeshes := TSceneMeshes.Create(FDevice);
    FObjects := TObjectModels.Create(FMeshes);
    FSceneLights := TSceneLights.Create(FDevice);
    FMaterials := TObjectMaterials.Create;

    FTextureCabinet := TTextureCabinet.Create(FDevice, FDisplaySize, TTextureFidelity.High, 8);

    // Configure Ambient Occlusion effect.
    LAmbientOcclusion := FTextureCabinet.AmbientOcclusion;
    LAmbientOcclusion.KernelBias := 0.05;
    LAmbientOcclusion.Strength := 1.0;
    FTextureCabinet.AmbientOcclusion := LAmbientOcclusion;

    FShadowCastingAtlas := TShadowCastingAtlas.Create(FDevice, Point2i(256, 256), TTechniqueShadows.EVSM, 8);

    FSceneDepthNormals := TScene.CreateDepthNormals(FDevice);
    FSceneDepthNormals.SetVertexElements(0, VertexElements);

    FSceneModeling := TScene.CreateModeling(FDevice);
    FSceneModeling.SetVertexElements(0, VertexElements);

    FViewport := FObjects.CreateView;
    FShadowCasterView := FObjects.CreateView;
    FShadowCasterView.View := TMatrix4f.LookAt(LightPosition, TVector3f.Zero, TVector3f.AxisY);
    FShadowCasterView.SetProjection(TMatrix4f.PerspectiveFOVY(Pi * 0.25, 1.0, 100.0, 1400.0,
      LDepthClipNegative), LDepthClipNegative);

    FShadowCastingAtlas.Add(Point2i(256, 256));

    FCamera := TActorCamera.Create(Vector3f(0.0, 0.0, -40.0), Vector3f(Pi * 0.25, -Pi * 0.25, 0.0), 440.0);
    FCamera.Constraints := TCameraConstraints.Create(
      Vector4f(-200.0, -100.0, -200.0,  100.0),
      Vector4f( 200.0,  100.0,  200.0, 1400.0),
      Vector4f(Pi * 0.1, 0.0, 0.0, 0.0),
      Vector4f(Pi * 0.4, 0.0, 0.0, 0.0));

    CreateAndLoadMeshes;
    CreateObjects;
    CreateLightsAndMaterials;
  except
    on E: EAfterwarpException do
    begin
      MessageDlg(E.Message, mtError, [mbOK], 0);
      Application.Terminate;
      Exit;
    end;
  end;
  FShadowMapNeedsUpdate := True;
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  FSceneModeling.Free;
  FSceneDepthNormals.Free;
  FShadowCastingAtlas.Free;
  FTextureCabinet.Free;
  FMaterials.Free;
  FSceneLights.Free;
  FObjects.Free;
  FMeshes.Free;
  FCamera.Free;
  FGrapher.Free;
  FTextRenderer.Free;
  FCanvas.Free;
  FSwapChain.Free;
  FDevice.Free;
end;

procedure TMainForm.RenderingControlResize(Sender: TObject);
begin
  FDisplaySize := Point2i(FRenderingControl.Width, FRenderingControl.Height);
  FSwapChain.Resize(FDisplaySize);
  FTextureCabinet.Size := FDisplaySize;
  FRenderingControl.Invalidate;
end;

procedure TMainForm.RenderingControlPaint(Sender: TObject);
var
  LCabinetAttributes: TTextureCabinetAttributes;
  LDepthClipNegative: Boolean;
  LFontParams: TFontParameters;
  LTechString: string;
  LAttributes: TSceneAttributes;
  LShadowCaster: TShadowCaster;
  LView: TObjectModelView;
  LRenderState: TRenderingState;
  I: Integer;
begin
  LDepthClipNegative := FDevice.Behavior and TDeviceBehavior.DepthClipNegative <> 0;
  FDisplayScale := PixelsPerInch / 96.0;

  // Perform light assignment for 3D scene.
  FSceneLights.ViewSize := FDisplaySize;

  // Only one light is used for the entire scene, so only minimal light culling is required.
  FSceneLights.ClustersCullingMode := TClustersCullingMode.Performance;
  FSceneLights.ClusterSize := 512;
  FSceneLights.DepthSlices := 1;
  FSceneLights.Execute(FCamera.View, FProjection);

  // Perform object selection.
  FProjection := TMatrix4f.PerspectiveFOVY(Pi * 0.25, FDisplaySize.X / FDisplaySize.Y,
    10.0, 2000.0, LDepthClipNegative);

  FViewport.View := FCamera.View;
  FViewport.SetProjection(FProjection, LDepthClipNegative);

  if FViewport.Update then
  begin
    FMouseRay := TRay.Create(FMousePos, FDisplaySize, FCamera.View.Inverse, FProjection);
    FSelected := FViewport.Select(FMouseRay);
  end;

  // Update texture cabinet's attributes.
  LCabinetAttributes := [TTextureCabinetAttribute.Bloom
    , TTextureCabinetAttribute.BloomCubic
    , TTextureCabinetAttribute.AmbientOcclusion
    , TTextureCabinetAttribute.Glassy
    , TTextureCabinetAttribute.GlassyFrosted
  ];

  if FDisplayScale >= 2.0 then
  begin // On 4K display, apply downscale for bloom and AO.
    LCabinetAttributes := LCabinetAttributes + [
      TTextureCabinetAttribute.BloomDownscale,
      TTextureCabinetAttribute.AmbientOcclusionDownscale];
  end;

  FTextureCabinet.Attributes := LCabinetAttributes;

  // Configure scene's attributes.
  LAttributes := [TSceneAttribute.Normals, TSceneAttribute.Coloring, TSceneAttribute.ShadowsCubic,
    TSceneAttribute.SinglePass];

  // Render shadow map.
  if FShadowMapNeedsUpdate then
  begin
    FShadowCastingAtlas.BorderFill;

    LShadowCaster := FShadowCastingAtlas.Casters[0];
    LShadowCaster.ViewProjection := FShadowCasterView.ViewProjection;

    FShadowCasterView.Update;
    LShadowCaster.Clear;
    LShadowCaster.BeginScene;
    try
      FSceneDepthNormals.Attributes := [];
      FSceneDepthNormals.View := FShadowCasterView.View;
      FSceneDepthNormals.Projection := FShadowCasterView.Projection;

      FSceneDepthNormals.BeginScene;
      try
        RenderObjectsOpaque(FSceneDepthNormals, FShadowCasterView);
      finally
        FSceneDepthNormals.EndScene;
      end;
    finally
      LShadowCaster.EndScene;
    end;
    LShadowCaster.Filter;
    FShadowMapNeedsUpdate := False;
  end;

  // Render main scene.
  FTextureCabinet.Clear(TTextureCabinetPass.Color, FloatColor($FFE0E0E0));
  FTextureCabinet.BeginScene(TTextureCabinetPass.Color);
  try
    FSceneModeling.Attributes := LAttributes;
    FSceneModeling.View := FCamera.View;
    FSceneModeling.Projection := FProjection;

    FSceneModeling.Lights := FSceneLights;
    FSceneModeling.ShadowCastingAtlas := FShadowCastingAtlas;
    FSceneModeling.TextureCabinet := FTextureCabinet;

    FSceneModeling.Prepare;
    FSceneModeling.BeginScene;
    try
      RenderObjectsOpaque(FSceneModeling, FViewport);
    finally
      FSceneModeling.EndScene;
    end;
  finally
    FTextureCabinet.EndScene;
  end;
  FTextureCabinet.Filter(TTextureCabinetFilterType.Occlusion, FProjection);

  if FSelected.Handle <> nil then
  begin
    // Render object's selection using grapher module.
    FTextureCabinet.BeginScene(TTextureCabinetPass.Color);
    try
      FGrapher.Transform := FCamera.View * FProjection;
      FGrapher.BeginScene;
      try
        LRenderState := FDevice.RenderingState;
        LRenderState.DepthBias := 1.0;
        LRenderState.SlopeDepthBias := 1.0;
        FDevice.RenderingState := LRenderState;

        FGrapher.BoundingBox(FSelected.Transform[TModelTransform.GlobalVolume], $FFFFFFFF, 6.0);
        FGrapher.Flush;

        LRenderState := FDevice.RenderingState;
        LRenderState.DepthBias := 5.0;
        LRenderState.SlopeDepthBias := 5.0;
        FDevice.RenderingState := LRenderState;

        FGrapher.BoundingBox(FSelected.Transform[TModelTransform.GlobalVolume], $FF000000, 3.0);
      finally
        FGrapher.EndScene;
      end;
    finally
      FTextureCabinet.EndScene;
    end;
  end;
  FTextureCabinet.Filter(TTextureCabinetFilterType.Bloom);

  // Render semi-transparent objects.
  FTextureCabinet.Clear(TTextureCabinetPass.Glassy);
  FTextureCabinet.BeginScene(TTextureCabinetPass.Glassy);
  try
    FSceneModeling.Attributes := LAttributes + [TSceneAttribute.Glassy];

    FSceneModeling.BeginScene;
    try
      RenderObjectsGlassy(FSceneModeling, FViewport);
    finally
      FSceneModeling.EndScene;
    end;
  finally
    FTextureCabinet.EndScene;
  end;
  FTextureCabinet.Filter(TTextureCabinetFilterType.Glassy);

  FSwapChain.BeginScene;
  try
    FDevice.Clear([TClearLayer.Color], FloatColor($FF404040));
    FTextureCabinet.Present;

    FCanvas.BeginScene;
    try
      FCanvas.Attributes := [];
      FCanvas.SamplerState := TCanvasSamplerState.Default;
      FCanvas.ContextState := TCanvasContextState.FlatText;

      LFontParams := TFontParameters.Create('Segoe UI', 16.0 * FDisplayScale, TFontWeight.Medium);
      LFontParams.Effect.FillBrightness := 0.0;
      LFontParams.Effect.BorderBrightness := 1.0;
      LFontParams.Effect.BorderType := TFontBorder.SemiHeavy;
      LFontParams.Effect.ShadowBrightness := 0.75;
      LFontParams.Effect.ShadowOpacity := 1.0;
      FTextRenderer.FontParameters := LFontParams;

      if FDevice.Technology = TDeviceTechnology.Direct3D then
        LTechString := 'Direct3D (' + IntToStr(FDevice.TechVersion shr 8) + '.' +
          IntToStr((FDevice.TechVersion shr 4) and $0F) + '), features: '
      else
        LTechString := 'OpenGL (' + IntToStr(FDevice.TechVersion shr 8) + '.' +
          IntToStr((FDevice.TechVersion shr 4) and $0F) + '+), features: ';

      LTechString := LTechString + IntToStr(FDevice.TechFeatureVersion shr 8) + '.' +
        IntToStr((FDevice.TechFeatureVersion shr 4) and $0F);

      FTextRenderer.Draw(Point2f(8.0 * FDisplayScale, 4.0 * FDisplayScale), Utf8String(LTechString),
        ColorPair($A0FFFFFF));
    finally
      FCanvas.EndScene;
    end;
  finally
    FSwapChain.EndScene;
  end;
{$IFNDEF MSWINDOWS}
  TOpenGLControl(FRenderingControl).SwapBuffers;
{$ENDIF}
end;

procedure TMainForm.RenderingControlMouseDown(Sender: TObject; Button: Controls.TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  LPosition: TVector3f;
begin
  FMousePos := Point2i(X, Y);
  LPosition := Vector3f(FMousePos.X, FMousePos.Y, 0.0);

  if Button = mbLeft then
    FCamera.Command(TCameraCommand.Rotation, LPosition, FDisplaySize)
  else
    FCamera.Command(TCameraCommand.Dragging, LPosition, FDisplaySize);
end;

procedure TMainForm.RenderingControlMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
  LPrevMousePos: TPoint2i;
  LPosition: TVector3f;
begin
  LPrevMousePos := FMousePos;
  FMousePos := Point2i(X, Y);
  LPosition := Vector3f(FMousePos.X, FMousePos.Y, 0.0);

  if FCamera.Command(TCameraCommand.Update, LPosition, FDisplaySize) then
    FRenderingControl.Invalidate;

  if LPrevMousePos <> FMousePos then
    FRenderingControl.Invalidate;
end;

procedure TMainForm.RenderingControlMouseUp(Sender: TObject; Button: Controls.TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  LPosition: TVector3f;
begin
  LPosition := Vector3f(FMousePos.X, FMousePos.Y, 0.0);
  FCamera.Command(TCameraCommand.Stop, LPosition, FDisplaySize);
end;

procedure TMainForm.RenderingControlMouseWheelUp(Sender: TObject; Shift: TShiftState; MousePos: TPoint;
  var Handled: Boolean);
begin
  FCamera.Zoom(FMousePos, FDisplaySize, FProjection, -16.0);
  FRenderingControl.Invalidate;
end;

procedure TMainForm.RenderingControlMouseWheelDown(Sender: TObject; Shift: TShiftState; MousePos: TPoint;
  var Handled: Boolean);
begin
  FCamera.Zoom(FMousePos, FDisplaySize, FProjection, 16.0);
  FRenderingControl.Invalidate;
end;

procedure TMainForm.RenderObjectsOpaque(const AScene: TScene; const AView: TObjectModelView);
var
  I: Integer;
  LObject: TObjectModel;
  LMesh: TSceneMesh;
  LMaterial: TObjectMaterial;
  LInitialState, LRenderingState: TRenderingState;
begin
  for I := 0 to AView.ObjectCount - 1 do
  begin
    LObject := AView.Objects[I];

    // Skip object if such is invalid, or transparent. An exception to this is when shadow map is
    // being rendered - in this case, all objects are rendered so they cast shadows.
    if ((TModelAttribute.Transparent in LObject.Attributes) and
      ((AScene.Handle <> FSceneDepthNormals.Handle))) then
      Continue;

    LMesh := LObject.Mesh;
    if LMesh.Handle = nil then
      Continue;

    AScene.World := LObject.Transform[TModelTransform.GlobalModel];

    if AScene.Handle = FSceneModeling.Handle then
    begin
      LMaterial := FMaterials[LObject.Material]^;
      LMaterial.AmbientColor := LMaterial.AmbientColor * LObject.Color.RGB;
      LMaterial.AlbedoColor := LMaterial.AlbedoColor * LObject.Color;
      AScene.Material := LMaterial;
    end;

    LInitialState := FDevice.RenderingState;
    try
      if LObject.Name = 'Table' then
      begin // Table has a hole in it, so should be rendered as double-sided.
        LRenderingState := FDevice.RenderingState;
        LRenderingState.CullFace := TTriangleFace.None;
        FDevice.RenderingState := LRenderingState;
      end;

      LMesh.Model.Draw(AScene.SceneProgram);
    finally
      FDevice.RenderingState := LInitialState;
    end;
  end;
end;

procedure TMainForm.RenderObjectsGlassy(const AScene: TScene; const AView: TObjectModelView);
var
  I: Integer;
  LObject: TObjectModel;
  LMesh: TSceneMesh;
  LColor: TFloatColor;
  LMaterial: TObjectMaterial;
begin
  for I := 0 to AView.ObjectCount - 1 do
  begin
    LObject := AView.Objects[I];
    if not (TModelAttribute.Transparent in LObject.Attributes) then
      Continue;

    LMesh := LObject.Mesh;
    if LMesh.Handle = nil then
      Continue;

    AScene.World := LObject.Transform[TModelTransform.GlobalModel];

    LColor := LObject.Color;
    LColor.Alpha := 0.75;

    LMaterial := FMaterials[LObject.Material]^;
    LMaterial.AmbientColor := LMaterial.AmbientColor * LColor.RGB;
    LMaterial.AlbedoColor := LMaterial.AlbedoColor * LColor;
    AScene.Material := LMaterial;

    LMesh.Model.Draw(AScene.SceneProgram);
  end;
end;

procedure TMainForm.CreateObjects;
const
  ChairFront = 80.0;
  ChairSpacing = 80.0;
  ChairSideSpacing = 210.0;
  GlassFront = 40.0;
  GlassSpacing = 80.0;
  GlassSideSpacing = 170.0;
var
  LFloor, LTable, LTableGlass, LFlowerPot, LLaptop, LChair, LWineGlass: TObjectModel;
  I, LIndex, LSide: Integer;
  LPosition: TVector3f;
begin
  LFloor := FObjects.Add('Floor');
  LFloor.MeshName := 'Plane';
  LFloor.Material := 0;
  LFloor.Color := FloatColor($FFE0E0E0);
  LFloor.Attributes := [TModelAttribute.Visible];
  LFloor.Transform[TModelTransform.Local] := TMatrix4f.Translate(0.0, -0.01, 0.0);

  LTable := FObjects.Add('Table');
  LTable.MeshName := 'Table';
  LTable.Material := 4;

  LTableGlass := FObjects.Add('TableGlass');
  LTableGlass.Parent := LTable;
  LTableGlass.MeshName := 'TableGlass';
  LTableGlass.Material := 3;
  LTableGlass.Attributes := [TModelAttribute.Selectable, TModelAttribute.Transparent,
    TModelAttribute.Visible];
  LTableGlass.Transform[TModelTransform.Local] := TMatrix4f.Translate(0.0, 65.5, 0.0);

  LFlowerPot := FObjects.Add('FlowerPot');
  LFlowerPot.Parent := LTable;
  LFlowerPot.MeshName := 'FlowerPot';
  LFlowerPot.Material := 1;
  LFlowerPot.Transform[TModelTransform.Local] := TMatrix4f.Translate(0.0, LTable.Size.Y, -50.0);

  LLaptop := FObjects.Add('Laptop');
  LLaptop.Parent := LTable;
  LLaptop.MeshName := 'Laptop';
  LLaptop.Material := 1;
  LLaptop.Transform[TModelTransform.Local] := TMatrix4f.Translate(36.0, LTable.Size.Y, 120.0);

  for I := 0 to 9 do
  begin
    LChair := FObjects.Add('Chair' + IntToStr(I));
    LChair.MeshName := 'Chair';
    LChair.Material := 1;

    if I < 8 then
    begin
      LIndex := I mod 4;
      LSide := I div 4;
      LChair.Transform[TModelTransform.Local] :=
        TMatrix4f.RotateY((3 + LSide * 2) * Pi * 0.5) *
        TMatrix4f.Translate(ChairFront - ChairFront * LSide * 2.0, 0.0, (LIndex - 1.5) * ChairSpacing);
    end
    else
    begin
      LSide := (I - 8) mod 2;
      LChair.Transform[TModelTransform.Local] :=
        TMatrix4f.RotateY((2 - LSide) * Pi) *
        TMatrix4f.Translate(0.0, 0.0, (LSide - 0.5) * ChairSideSpacing * 2.0);
    end;
  end;

  for I := 0 to 9 do
  begin
    LWineGlass := FObjects.Add('WineGlass' + IntToStr(I));
    LWineGlass.MeshName := 'WineGlass';
    LWineGlass.Material := 2;
    LWineGlass.Attributes := [TModelAttribute.Selectable, TModelAttribute.Transparent, TModelAttribute.Visible];

    if I < 8 then
    begin
      LIndex := I mod 4;
      LSide := I div 4;
      LPosition := Vector3f(GlassFront - GlassFront * LSide * 2.0, LTable.Size.Y,
        (LIndex - 1.5) * GlassSpacing);

      if I = 3 then
        LPosition.Z := LPosition.Z + 30.0;

      LWineGlass.Transform[TModelTransform.Local] := TMatrix4f.Translate(LPosition);
    end
    else
    begin
      LSide := (I - 8) mod 2;
      LWineGlass.Transform[TModelTransform.Local] :=
        TMatrix4f.Translate(0.0, LTable.Size.Y, (LSide - 0.5) * GlassSideSpacing * 2.0);
    end;
  end;
end;

procedure TMainForm.CreateAndLoadMeshes;
const
  MeshNames: array[0..5] of string = ('Chair', 'Table', 'TableGlass', 'FlowerPot', 'Laptop', 'WineGlass');
  MeshScale: Single = 8.0;
var
  LMeshBuffer: TMeshBuffer;
  LOptions: Cardinal;
  I: Integer;
begin
  LMeshBuffer := TMeshBuffer.Create;
  try
    // Plane
    LMeshBuffer.Plane(1, 1, TVector3f.Zero, Vector3f(400.0, 0.0, 0.0), Vector3f(0.0, 0.0, 800.0),
      Vector3f(0.0, 1.0, 0.0), TPoint2f.Zero, TPoint2f.Unity, TFloatColor.White);

    FMeshes.AddFromBuffer('Plane', LMeshBuffer, nil, VertexElements);

    // Cube
    LMeshBuffer.Clear;
    LMeshBuffer.Cube(1, 1, 1, TVector3f.Zero, Vector3f(1.0, 0.0, 0.0), Vector3f(0.0, 1.0, 0.0),
      Vector3f(0.0, 0.0, 1.0), TPoint2f.Zero, TPoint2f.Unity, TFloatColor.White, True);

    FMeshes.AddFromBuffer('Cube', LMeshBuffer, nil, VertexElements);
  finally
    LMeshBuffer.Free;
  end;
  LOptions := TMeshLoadingOption.MaterialVertexColor;

  for I := 0 to High(MeshNames) do
    FMeshes.AddFromFile(MeshNames[I], MeshesBasePath + MeshNames[I] + '.obj', nil, VertexElements, 0, 0,
      LOptions, MeshScale);
end;

procedure TMainForm.CreateLightsAndMaterials;
var
  LLight: PSceneLight;
  LMaterial: PObjectMaterial;
begin
  LLight := FSceneLights.Add;
  LLight.AmbientColor := FloatColorRGB($FFFFFF);
  LLight.AlbedoColor := FloatColorRGB($FFFFFF);
  LLight.SpecularColor := FloatColorRGB($FFFFFF);
  LLight.SpecularExponent := 1.0;
  LLight.Intensity := 1.0;
  LLight.Angle := Pi;
  LLight.Position := LightPosition;
  LLight.ShadowCaster := 0;

  LMaterial := FMaterials.Add;
  LMaterial.AmbientColor := FloatColorRGB(1.0, 1.0, 1.0);
  LMaterial.AlbedoColor := FloatColor(0.0, 0.0, 0.0, 0.0);
  LMaterial.SpecularColor := FloatColorRGB(0.0, 0.0, 0.0);
  LMaterial.Shadows := 0.5;
  LMaterial.Occlusion := 0.5;
  LMaterial.Technique := TTechniqueLighting.Phong;

  LMaterial := FMaterials.Add;
  LMaterial.AmbientColor := FloatColorRGB(0.25, 0.25, 0.25);
  LMaterial.AlbedoColor := FloatColor(1.0, 1.0, 1.0, 0.5);
  LMaterial.SpecularColor := FloatColorRGB(1.0, 1.0, 1.0);
  LMaterial.SpecularExponent := 4.0;
  LMaterial.Shadows := 0.5;
  LMaterial.Occlusion := 0.5;
  LMaterial.Roughness := Vector3f(0.8, 1.2, 0.4);
  LMaterial.Technique := TTechniqueLighting.Phong;

  LMaterial := FMaterials.Add;
  LMaterial.AmbientColor := FloatColorRGB(0.5, 0.5, 0.5);
  LMaterial.AlbedoColor := FloatColor(1.0, 1.0, 1.0, 0.25);
  LMaterial.SpecularColor := FloatColorRGB(1.0, 1.0, 1.0);
  LMaterial.SpecularExponent := 16.0;
  LMaterial.Shadows := 0.0;
  LMaterial.Occlusion := 0.0;
  LMaterial.Roughness := Vector3f(1.8, 1.2, 0.4);
  LMaterial.Technique := TTechniqueLighting.Phong;
  LMaterial.FrostedGlass := 4.0;

  LMaterial := FMaterials.Add;
  LMaterial.AmbientColor := FloatColorRGB(0.25, 0.25, 0.25);
  LMaterial.AlbedoColor := FloatColor(1.0, 1.0, 1.0, 0.5);
  LMaterial.SpecularColor := FloatColorRGB(1.0, 1.0, 1.0);
  LMaterial.SpecularExponent := 4.0;
  LMaterial.Shadows := 0.75;
  LMaterial.Occlusion := 0.0;
  LMaterial.Roughness := Vector3f(0.8, 1.2, 0.4);
  LMaterial.Technique := TTechniqueLighting.Phong;
  LMaterial.FrostedGlass := 3.0;

  LMaterial := FMaterials.Add;
  LMaterial.AmbientColor := FloatColorRGB(0.25, 0.25, 0.25);
  LMaterial.AlbedoColor := FloatColor(1.0, 1.0, 1.0, 0.5);
  LMaterial.SpecularColor := FloatColorRGB(0.5, 0.5, 0.5);
  LMaterial.SpecularExponent := 4.0;
  LMaterial.Shadows := 0.5;
  LMaterial.Occlusion := 0.5;
  LMaterial.Roughness := Vector3f(0.8, 1.2, 0.4);
  LMaterial.Technique := TTechniqueLighting.Minnaert;
end;

procedure TMainForm.CreateRenderingControl;
begin
{$IFDEF MSWINDOWS}
  FRenderingControl := TRenderingControl.Create(nil);
  FRenderingControl.Parent := Self;
  FRenderingControl.Align := alClient;
  FRenderingControl.Width := 256;
  FRenderingControl.Height := 256;

  with FRenderingControl as TRenderingControl do
  begin
    OnPaint := RenderingControlPaint;
    OnResize := RenderingControlResize;
    OnMouseDown := RenderingControlMouseDown;
    OnMouseMove := RenderingControlMouseMove;
    OnMouseUp := RenderingControlMouseUp;
    OnMouseWheelUp := RenderingControlMouseWheelUp;
    OnMouseWheelDown := RenderingControlMouseWheelDown;
  end;
{$ELSE}
  FRenderingControl := TOpenGLControl.Create(nil);
  FRenderingControl.Parent := Self;
  FRenderingControl.Align := alClient;
  FRenderingControl.Width := 256;
  FRenderingControl.Height := 256;

  with FRenderingControl as TOpenGLControl do
  begin
    OpenGLMajorVersion := 3;
    OpenGLMinorVersion := 3;
    DepthBits := 0;
    OnPaint := RenderingControlPaint;
    OnResize := RenderingControlResize;
    OnMouseDown := RenderingControlMouseDown;
    OnMouseMove := RenderingControlMouseMove;
    OnMouseUp := RenderingControlMouseUp;
    OnMouseWheelUp := RenderingControlMouseWheelUp;
    OnMouseWheelDown := RenderingControlMouseWheelDown;

    MakeCurrent;
  end;
{$ENDIF}
end;

end.
