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.
//  * Color dithering to reduce quantization errors when converting from 64-bits per pixel to 32-bit and
//    remove visible banding effects.
//  * Object layers, which define what objects are currently visible. This enables quick switching between
//    different views and/or floors.
//  * Optional orthographic projection, where all objects appear in their original sizes.
//  * Third-person view camera with location-based zoom (zooms in or out where the mouse is pointing at).

interface

uses
  Winapi.Windows, System.Types, System.Classes, Vcl.Graphics, Vcl.Forms, Vcl.Dialogs, Vcl.Controls,
  PXT.Types, PXT.Graphics, PXT.Controls;

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

    FDevice: TDevice;
    FSwapChain: TSwapChain;
    FCanvas: TCanvas;
    FTextRenderer: TTextRenderer;

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

    FTextureCabinet: TTextureCabinet;
    FColorDithering: TColorDithering;

    FSceneDepthNormals: TScene;
    FSceneModeling: TScene;

    FCamera: TActorCamera;
    FViewport: TObjectModelView;
    FProjection: TMatrix4f;

    FLayers: Cardinal;
    FCameraZoom: Single;
    FOrthographic: Boolean;

    procedure RenderingControlResize(Sender: TObject);
    procedure RenderingControlPaint(Sender: TObject);
    procedure RenderingControlMouseDown(Sender: TObject; Button: Vcl.Controls.TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure RenderingControlMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
    procedure RenderingControlMouseUp(Sender: TObject; Button: Vcl.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 RenderingControlKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure AdjustCameraZoom(const AOffset: Single);
    procedure RenderObjects(const AScene: TScene; const AView: TObjectModelView);
    procedure RenderObjectsGlassy(const AScene: TScene; const AView: TObjectModelView);
    procedure CreateObjects;
    procedure CreateLightsAndMaterials;
    procedure CreateAndLoadMeshes;
    procedure CreateRenderingControl;
  public
  end;

var
  MainForm: TMainForm;

implementation
{$R *.dfm}

uses
  System.SysUtils, System.Math, PXT.Headers, PXT.Graphics.Consts;

const
  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
    FDevice := TDevice.Create;
    FSwapChain := TSwapChain.Create(FDevice, FRenderingControl.WindowHandle, FDisplaySize);

    FCanvas := TCanvas.Create(FDevice);
    FTextRenderer := TTextRenderer.Create(FCanvas, Point2i(256, 256), TPixelFormat.LA8);

    FMeshes := TSceneMeshes.Create(FDevice);
    FObjects := TObjectModels.Create(FMeshes);
    FLights := TSceneLights.Create(FDevice);
    FMaterials := TObjectMaterials.Create;

    FTextureCabinet := TTextureCabinet.Create(FDevice, FDisplaySize, TTextureFidelity.High, 8);
    // Configure Ambient Occlusion parameters.
    LAmbientOcclusion := FTextureCabinet.AmbientOcclusion;
    LAmbientOcclusion.Radius := 32.0;
    LAmbientOcclusion.KernelBias := 0.05;
    LAmbientOcclusion.Strength := 1.0;
    FTextureCabinet.AmbientOcclusion := LAmbientOcclusion;

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

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

    FColorDithering := TColorDithering.Create(FDevice);
    FViewport := FObjects.CreateView;

    FCamera := TActorCamera.Create(TVector3f.Zero, Vector3f(Pi * 0.25, -Pi * 0.25, 0.0), 400.0);
    FCamera.Constraints := TCameraConstraints.Create(
      Vector4f(-300.0, -150.0, -300.0,  200.0),
      Vector4f( 300.0,  150.0,  300.0, 1500.0),
      Vector4f(Pi * 0.01, 0.0, 0.0, 0.0),
      Vector4f(Pi * 0.49, 0.0, 0.0, 0.0));

    CreateAndLoadMeshes;
    CreateLightsAndMaterials;
    CreateObjects;

    FCameraZoom := FCamera.Distance;
    FLayers := 1;
  except
    on E: EAfterwarpException do
    begin
      MessageDlg(E.Message, mtError, [mbOK], 0);
      Application.Terminate;
      Exit;
    end;
  end;
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  FCamera.Free;
  FObjects.Free;
  FMeshes.Free;
  FMaterials.Free;
  FColorDithering.Free;

  FSceneModeling.Free;
  FSceneDepthNormals.Free;
  FTextureCabinet.Free;
  FLights.Free;

  FTextRenderer.Free;
  FCanvas.Free;
  FSwapChain.Free;
  FDevice.Free;
end;

procedure TMainForm.RenderingControlResize(Sender: TObject);
begin
  if FSwapChain.Handle <> nil then
  begin
    FDisplaySize := Point2i(FRenderingControl.Width, FRenderingControl.Height);
    FSwapChain.Resize(FDisplaySize);
    FTextureCabinet.Size := FDisplaySize;
  end;
end;

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

  if FOrthographic then
    FProjection := TMatrix4f.OrthogonalVOL(FCameraZoom, FCameraZoom * FDisplaySize.Y / FDisplaySize.X,
      10.0, 1800.0)
  else
    FProjection := TMatrix4f.PerspectiveFOVY(Pi * 0.25, FDisplaySize.X / FDisplaySize.Y,
      10.0, 1800.0, LDepthClipNegative);

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

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

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

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

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

  // Render depth pre-pass.
  FTextureCabinet.Clear(TTextureCabinetPass.Depth);
  FTextureCabinet.BeginScene(TTextureCabinetPass.Depth);
  try
    FSceneDepthNormals.Attributes := LAttributes;
    FSceneDepthNormals.View := FCamera.View;
    FSceneDepthNormals.Projection := FProjection;

    FSceneDepthNormals.BeginScene;
    try
      RenderObjects(FSceneDepthNormals, FViewport);
    finally
      FSceneDepthNormals.EndScene;
    end;
  finally
    FTextureCabinet.EndScene;
  end;
  FTextureCabinet.Filter(TTextureCabinetFilterType.Occlusion, FProjection);

  // Render main scene.
  FTextureCabinet.Clear(TTextureCabinetPass.Color, FloatColor($FF303030));
  FTextureCabinet.BeginScene(TTextureCabinetPass.Color);
  try
    FSceneModeling.Lights := FLights;
    FSceneModeling.TextureCabinet := FTextureCabinet;

    FSceneModeling.Attributes := LAttributes;
    FSceneModeling.View := FCamera.View;
    FSceneModeling.Projection := FProjection;

    FSceneModeling.Prepare;
    FSceneModeling.BeginScene;
    try
      RenderObjects(FSceneModeling, FViewport);
    finally
      FSceneModeling.EndScene;
    end;
  finally
    FTextureCabinet.EndScene;
  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);

  // Render on the form.
  FSwapChain.BeginScene;
  try
    // Clear the screen to discard previous swap-chain contents, which may improve performance.
    FDevice.Clear([TClearLayer.Color], FloatColor($FF404040));

    FColorDithering.Format := TColorDitheringFormat.RGB8;
    FColorDithering.Execute(FTextureCabinet.FinalTexture);

    // Comment above two lines and uncomment the following to render without dithering:
    // FTextureCabinet.Present;

    // Draw 2D text on top of 3D scene.
    FCanvas.BeginScene;
    try
      FCanvas.Attributes := [];
      FCanvas.SamplerState := TCanvasSamplerState.Default;
      FCanvas.ContextState := TCanvasContextState.FlatText;

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

      FTextRenderer.DrawAligned(Point2f(FDisplaySize.X - 8.0 * FDisplayScale, 4.0 * FDisplayScale),
        'Space - switch layers, Enter - orthographic, Up/Down - adjust "frosted glass" effect.',
        ColorPair($FFA5E7B8, $FF77FFCE), TTextAlignment.Ending, TTextAlignment.Start);

      FTextRenderer.DrawAligned(Point2f(FDisplaySize.X * 0.5, FDisplaySize.Y - 4.0 * FDisplayScale),
        'Left mouse button: rotate scene, right button: move scene, wheel: zoom.',
        ColorPair($FFA5E7B8, $FF77FFCE), TTextAlignment.Middle, TTextAlignment.Ending);

      LFontParams := TFontParameters.Create('Segoe UI', 16.0 * FDisplayScale, TFontWeight.SemiBold);
      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;
end;

procedure TMainForm.RenderingControlMouseDown(Sender: TObject; Button: Vcl.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
  LPosition: TVector3f;
begin
  FMousePos := Point2i(X, Y);
  LPosition := Vector3f(FMousePos.X, FMousePos.Y, 0.0);

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

procedure TMainForm.RenderingControlMouseUp(Sender: TObject; Button: Vcl.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
  AdjustCameraZoom(-16.0);
  FRenderingControl.Invalidate;
end;

procedure TMainForm.RenderingControlMouseWheelDown(Sender: TObject; Shift: TShiftState; MousePos: TPoint;
  var Handled: Boolean);
begin
  AdjustCameraZoom(16.0);
  FRenderingControl.Invalidate;
end;

procedure TMainForm.RenderingControlKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
var
  LObject: TObjectModel;
  LMaterial: PObjectMaterial;
begin
  if Key = VK_SPACE then
  begin
    if FLayers = $01 then
    begin
      LObject := FObjects['GroundFloorWalls'];
      LObject.Attributes := LObject.Attributes - [TModelAttribute.Transparent];
      FLayers := $03;
    end
    else if FLayers = $03 then
      FLayers := $07
    else if FLayers = $07 then
    begin
      LObject := FObjects['GroundFloorWalls'];
      LObject.Attributes := LObject.Attributes + [TModelAttribute.Transparent];
      FLayers := $01;
    end;
    FRenderingControl.Invalidate;
  end
  else if Key = VK_UP then
  begin
    LMaterial := FMaterials[2];
    LMaterial.FrostedGlass := Min(LMaterial.FrostedGlass + 0.5, 12.0);
    FRenderingControl.Invalidate;
  end
  else if Key = VK_DOWN then
  begin
    LMaterial := FMaterials[2];
    LMaterial.FrostedGlass := Max(LMaterial.FrostedGlass - 0.5, 0.0);
    FRenderingControl.Invalidate;
  end
  else if Key = VK_RETURN then
  begin
    FOrthographic := not FOrthographic;

    if FOrthographic then
      FCamera.Distance := 400.0
    else
      FCamera.Distance := FCameraZoom;

    // Reset camera position and rotation.
    FCamera.Position := TVector3f.Zero;
    FCamera.Rotation := Vector4f(Pi * 0.25, PI * -0.25, 0.0, 0.0);

    FRenderingControl.Invalidate;
  end
  else if Key = VK_ESCAPE then
    Close;
end;

procedure TMainForm.AdjustCameraZoom(const AOffset: Single);
var
  LAdjustedZoom: Single;
  LAdjustedProjection: TMatrix4f;
begin
  if FOrthographic then
  begin
    LAdjustedZoom := Saturate(FCameraZoom + AOffset, 100.0, 1500.0);
    if Abs(FCameraZoom - LAdjustedZoom) > VectorEpsilon then
    begin
      LAdjustedProjection := TMatrix4f.OrthogonalVOL(LAdjustedZoom, LAdjustedZoom * FDisplaySize.Y /
        FDisplaySize.X, 10.0, 1800.0);

      FCamera.ZoomOrtho(FMousePos, FDisplaySize, FCameraZoom, LAdjustedZoom, FProjection,
        LAdjustedProjection);

      FCameraZoom := LAdjustedZoom;
    end;
  end
  else
  begin
    FCamera.Zoom(FMousePos, FDisplaySize, FProjection, AOffset);
    FCameraZoom := FCamera.Distance;
  end;
end;

procedure TMainForm.RenderObjects(const AScene: TScene; const AView: TObjectModelView);
var
  LObject: TObjectModel;
  LMesh: TSceneMesh;
  LModel: TMeshModel;
begin
  for LObject in AView do
  begin
    if TModelAttribute.Transparent in LObject.Attributes then
      Continue; // Skip transparent objects.

    LMesh := LObject.Mesh;
    if LMesh.Handle = nil then
      Continue; // Object has mesh unassigned?

    LModel := LMesh.Model;
    if LModel.Handle = nil then
      Continue; // Mesh has rendering model unassigned?

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

    if AScene.Handle = FSceneModeling.Handle then
      AScene.Material := FMaterials[LObject.Material]^;

    LModel.Draw(AScene.SceneProgram);
  end;
end;

procedure TMainForm.RenderObjectsGlassy(const AScene: TScene; const AView: TObjectModelView);
var
  LObject: TObjectModel;
  LMesh: TSceneMesh;
  LModel: TMeshModel;
  LMaterial: TObjectMaterial;
begin
  for LObject in AView do
  begin
    if not (TModelAttribute.Transparent in LObject.Attributes) then
      Continue; // Skip non-transparent objects.

    LMesh := LObject.Mesh;
    if LMesh.Handle = nil then
      Continue; // Object has mesh unassigned?

    LModel := LMesh.Model;
    if LModel.Handle = nil then
      Continue; // Mesh has rendering model unassigned?

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

    LMaterial := FMaterials[LObject.Material]^;
    LMaterial.AlbedoColor.Alpha := 0.4;
    AScene.Material := LMaterial;

    LModel.Draw(AScene.SceneProgram);
  end;
end;

procedure TMainForm.CreateObjects;
var
  LObject: TObjectModel;
begin
  LObject := FObjects.Add('HouseFloor');
  LObject.MeshName := 'HouseFloor';
  LObject.Material := 0;
  LObject.Attributes := [TModelAttribute.Visible, TModelAttribute.Selectable,
    TModelAttribute.Hierarchyless];
  LObject.Layers := $01;

  LObject := FObjects.Add('GroundFloor');
  LObject.MeshName := 'GroundFloorNoWalls';
  LObject.Material := 1;
  LObject.Attributes := [TModelAttribute.Visible, TModelAttribute.Selectable];
  LObject.Layers := $01;

  LObject := FObjects.Add('GroundFloorWalls');
  LObject.MeshName := 'GroundFloorWalls';
  LObject.Material := 2;
  LObject.Attributes := [TModelAttribute.Visible, TModelAttribute.Selectable, TModelAttribute.Transparent];
  LObject.Layers := $01;
  LObject.Transform[TModelTransform.Local] := TMatrix4f.Translate(0.55, 0.0, -71.6);

  LObject := FObjects.Add('TopFloor');
  LObject.MeshName := 'TopFloorNoWalls';
  LObject.Material := 1;
  LObject.Attributes := [TModelAttribute.Visible, TModelAttribute.Selectable];
  LObject.Layers := $02;
  LObject.Transform[TModelTransform.Local] := TMatrix4f.Translate(0.0, 15.5, -280.0);

  LObject := FObjects.Add('TopFloorDivision');
  LObject.MeshName := 'TopFloorDivision';
  LObject.Material := 0;
  LObject.Attributes := [TModelAttribute.Visible, TModelAttribute.Selectable];
  LObject.Layers := $02;
  LObject.Transform[TModelTransform.Local] := TMatrix4f.Translate(0.0, 15.5, -280.0);

  LObject := FObjects.Add('TopFloorWalls');
  LObject.MeshName := 'TopFloorWalls';
  LObject.Material := 2;
  LObject.Attributes := [TModelAttribute.Visible, TModelAttribute.Selectable, TModelAttribute.Transparent];
  LObject.Layers := $02;
  LObject.Transform[TModelTransform.Local] := TMatrix4f.Translate(0.0, 96.0, -280.0);

  LObject := FObjects.Add('HouseWalls');
  LObject.MeshName := 'HouseWalls';
  LObject.Material := 1;
  LObject.Attributes := [TModelAttribute.Visible, TModelAttribute.Selectable];
  LObject.Layers := $04;
end;

procedure TMainForm.CreateLightsAndMaterials;
var
  LLight: PSceneLight;
  LMaterial: PObjectMaterial;
begin
  // Create default light.
  LLight := FLights.Add;
  LLight.AmbientColor := FloatColorRGB($00FFFFFF);
  LLight.AlbedoColor := FloatColorRGB($00FFFFFF);
  LLight.SpecularColor := FloatColorRGB($00FFFFFF);
  LLight.SpecularExponent := 1.0;
  LLight.Position := LightPosition;
  LLight.ShadowCaster := 0;

  // (0) Floor
  LMaterial := FMaterials.Add;
  LMaterial.AmbientColor := TFloatColorRGB.White;
  LMaterial.AlbedoColor := TFloatColor.Black;
  LMaterial.SpecularColor := TFloatColorRGB.Black;
  LMaterial.Occlusion := 1.0;

  // (1) Non-transparent objects
  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.Occlusion := 1.0;

  // (2) Plastic looking semi-transparent walls
  LMaterial := FMaterials.Add;
  LMaterial.AmbientColor := FloatColorRGB(0.25, 0.25, 0.25);
  LMaterial.AlbedoColor := FloatColor(0.5, 0.5, 0.5, 1.0);
  LMaterial.SpecularColor := FloatColorRGB(0.25, 0.25, 0.25);
  LMaterial.SpecularExponent := 16.0;
  LMaterial.Roughness := Vector3f(0.3, 1.2, 0.4);
  LMaterial.Technique := TTechniqueLighting.Minnaert;
  LMaterial.FrostedGlass := 4.0;
end;

procedure TMainForm.CreateAndLoadMeshes;
const
  BasePath = '../../../../Media/House/';
  MeshNames: array[0..6] of Utf8String = ('GroundFloorNoWalls', 'GroundFloorWalls', 'TopFloorNoWalls',
    'TopFloorWalls', 'TopFloorDivision', 'HouseWalls', 'HouseFloor');
  MeshScale: Single = 20.0;
var
  LOptions: Cardinal;
  I: Integer;
begin
  LOptions := TMeshLoadingOption.MaterialVertexColor;

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

procedure TMainForm.CreateRenderingControl;
begin
  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;
    OnKeyDown := RenderingControlKeyDown;
  end;
end;

end.