program NightBridge;
(*
 * 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:
//  * 306 simultaneous light sources, 304 of which are casting shadows in real-time!
//  * Around 2550 objects in the scene, includes multiple vehicles that are in a constant movement.
//    The vehicles also have a proper working headlights! All these objects are rendered efficiently
//    using "auto-draw" functions, which also perform automatic instancing.
//  * First-person view camera using Raw Mouse Input API and free world roaming.
//  * Spatial fog using relative eye distance and exponential formula.
//  * Realistic real-time water simulation.

// Note: this example uses Afterwarp API for window and application management, to leverage raw mouse input
// and optimized rendering loop.

uses
  Winapi.Windows, System.SysUtils, System.Math, PXT.Types, PXT.Headers, PXT.Graphics, PXT.Graphics.Consts;

{$SCOPEDENUMS ON}
{$R *.res}

type
  // Cumulative bits that define movement direction.
  TMovementDirection = (Forward, Backward, Left, Right, Up, Down);
  TMovementDirections = set of TMovementDirection;

  // Cumulative bits that define characteristics of light object.
  TLightTrackingAttribute = (
    // Shadow map needs to be re-rendered.
    UpdateNeeded,

    // Shadow map needs to be re-rendered without cars. When the car is too far away from the view, it stops
    // updating shadow maps for nearby lampposts; at this point, if there was a lamppost that had this car in
    // its shadow map, it will persist and not get updated as the car would have long gone. Therefore, once
    // the car stops shadow map updates, all its nearby lampposts get their shadow map updated for the last
    // time, except this time without any cars, to avoid having persistent car shadow artifacts.
    LateUpdateNeeded,

    // Shadow map needs to be re-rendered without vehicles, eventually. This attribute is set when a bridge
    // has been relocated to account for observer movement, so shadows use different projected matrix.
    UpdateQueued,

    // Light corresponds to a car headlight.
    Headlight);

  TLightTrackingAttributes = set of TLightTrackingAttribute;

  PMovementTracking = ^TMovementTracking;

  // Additional information added to each "car" for movement tracking.
  TMovementTracking = record
    // Parent bridge index.
    Bridge: Integer;

    // Indices to closest lampposts on each side.
    Lampposts: array[0..1] of Integer;
  end;

  PObjectLightTracking = ^TObjectLightTracking;

  // Additional parameters added to each "important" object in form of "payload".
  TObjectLightTracking = record
    // Light associated with a lamppost (if applies). In case of headlight, this is not assigned.
    Light: PSceneLight;

    // View for a shadow caster associated with the lamppost or headlight.
    View: TObjectModelView;

    // Shadow caster associated with the lamppost or headlight.
    Caster: TShadowCaster;

    // Cumulative attribute bits.
    Attributes: TLightTrackingAttributes;

    // Additional and optional information for car movement.
    Movement: PMovementTracking;
  end;

  TApplicationHelper = class
  private const
    // Parameters for controling camera movement.
    CameraAcceleration = 400.0;
    CameraMovementSpeed = 120.0;
    CameraMinimumAltitude = 430.0;

    // How fast cars travel.
    VehicleMovementSpeed = 100.0;

    // Position of an abstract light source (sun/moon) that will illuminate water and the bridge.
    LightPosition: TVector3f = (X: -16000.0; Y: 2000.0; Z: 4000.0);

    // Position relative to the origin of the bridge, where objects will be placed.
    BridgeAt: Single = 421.5;

    // Note: when increasing number of vehicles, bridges and/or lampposts, please remember to increase
    // "shadowMapAtlasSize" to be able to accomodate additional shadow maps required for extra lights!

    // Number of bridges. Increasing this number may have performance impact at negligible benefit due to
    // limited viewing distance.
    BridgeCount = 9;

    // Total number of vehicles over all bridges.
    VehicleCount = 8;

    // Number of lampposts on each side of the bridge.
    LamppostCount = 16;

    // The size of shadow mapping atlas. This must be sufficient to accomodate all shadow maps used by
    // lampposts and vehicle's headlights.
    ShadowMapAtlasSize: TPoint2i = (X: 2560; Y: 2048);

    // The size of shadow map for the lampposts and vehicles.
    ShadowMapSize: TPoint2i = (X: 128; Y: 128);

    // Calculated number of lights on each side of the bridge based on the number of lampposts.
    LightsPerSide = BridgeCount * LamppostCount;

    // Half of vehicles will go in one direction, whereas others in the opposite direction.
    VehicleCountHalf = (VehicleCount + 1) div 2;
  private type
    TSideLights = array[0..1] of Integer;
  private
    FApplication: TApplication;

    FDisplaySize: TPoint2i;
    FDisplayScale: Single;

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

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

    FTextureCabinet: TTextureCabinet;
    FShadowCastingAtlas: TShadowCastingAtlas;
    FSceneDepthNormals: TScene;
    FSceneModeling: TScene;
    FOceanSimulation: TOceanSimulation;
    FSpatialFog: TSpatialFog;

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

    FBridgeSize: TVector3f;
    FBridgeLength: Single;

    FMovementDirection: TMovementDirections;
    FVelocity: TVector3f;
    FLastSmoothMouse: TPoint2f;

    FShadowCastingAtlasPrepared: Boolean;
    FShadowMapsUpdated: Integer;
    FObjectsVisible: Integer;
    FDrawCalls: Integer;
    FKeyAlt: Integer;
    FMinimized: Boolean;
    FActive: Boolean;

    FLamppostLights: array[0..1, 0..(LightsPerSide - 1)] of PSceneLight;
    FBridges: array[0..(BridgeCount - 1)] of TObjectModel;
    FVehicles: array[0..(VehicleCount - 1)] of TObjectModel;

    function AppCreate(const AApplication: TApplication): Boolean;
    procedure AppDestroy(const AApplication: TApplication);
    procedure AppResize(const AApplication: TApplication);
    procedure AppRender(const AApplication: TApplication);
    procedure AppMouse(const AApplication: TApplication; const AEvent: TMouseEvent;
      const AButton: TMouseButton; const APosition: TPoint2i);
    procedure AppKey(const AApplication: TApplication; const AEvent: TKeyEvent; const AVirtualKey: Integer;
      const AKeyCode: Word);
    procedure AppStatus(const AApplication: TApplication; const AEvent: TApplicationEvent);
    function AppIdle(const AApplication: TApplication): Boolean;
    procedure RenderObjects(const AScene: TScene; const AView: TObjectModelView;
      const AExcludeVehicles: Boolean = False);
    procedure UpdateVehicleMovement(const ALatency: Double);
    procedure UpdateCameraMovement(const ALatency: Double);
    procedure UpdateBridges(const AStartup: Boolean = False);
    procedure SynchronizeLights;
    procedure CreateBridgeLampposts(const ABridge: TObjectModel; var ASideLights: TSideLights);
    procedure CreateBridgeObstacles(const ABridge: TObjectModel; var APRNG: TRandomSequence);
    procedure CreateVehicles(var APRNG: TRandomSequence);
    procedure CreateObjects;
    procedure CreateAndLoadMeshes;
    procedure CreateLightsAndMaterials;
    procedure ReleaseObjectLightTracking;
  public
    constructor Create;
    destructor Destroy; override;

    function Execute: Boolean;
  end;

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: -16000.0; Y: 2000.0; Z: 4000.0);

procedure DisplayErrorMessage(const AErrorText: string);
const
  InitErrorTitle: PChar = 'Initialization Error';
begin
  MessageBox(0, PChar(AErrorText), InitErrorTitle, MB_ICONERROR or MB_OK or MB_DEFAULT_DESKTOP_ONLY);
end;

constructor TApplicationHelper.Create;
begin
  inherited;

  FDisplaySize := Point2i(1280, 720);
  FApplication := TApplication.Create(GetModuleHandle(nil), 0, 0, 'NightBridgeWindowClass',
    FDisplaySize, TApplicationWindowState.FullScreen);

  FApplication.Title := 'Afterwarp: NightBridge example';
  FApplication.IconTitle := 'NightBridge';

  FApplication.OnCreate := AppCreate;
  FApplication.OnDestroy := AppDestroy;
  FApplication.OnResize := AppResize;
  FApplication.OnRender := AppRender;
  FApplication.OnMouse := AppMouse;
  FApplication.OnKey := AppKey;
  FApplication.OnStatus := AppStatus;
  FApplication.OnIdle := AppIdle;

  FDisplayScale := FApplication.WindowScale;
  FDisplaySize := FApplication.ClientRect.Size;
  FActive := True;
  FLastSmoothMouse := Point2f(NAN, NAN);
end;

destructor TApplicationHelper.Destroy;
begin
  FApplication.Free;
  inherited;
end;

function TApplicationHelper.AppCreate(const AApplication: TApplication): Boolean;
var
  LVsync: Boolean;
begin
  try
    FDevice := TDevice.Create(FApplication);

    LVsync := True;
    if FDevice.Behavior and TDeviceBehavior.VariableRateRefresh <> 0 then
      LVSync := False; // Enable Variable Rate Refresh.

    FSwapChain := TSwapChain.Create(FDevice, FApplication.WindowHandle, FDisplaySize, TPixelFormat.Unknown,
      TPixelFormat.Unknown, 0, LVSync);

    FApplication.SetIconsFromFiles(['../../../../Media/afterwarp.ico']);

    FCanvas := TCanvas.Create(FDevice);
    FTextRenderer := TTextRenderer.Create(FCanvas, Point2i(512, 512), 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);
    FShadowCastingAtlas := TShadowCastingAtlas.Create(FDevice, ShadowMapAtlasSize,
      TTechniqueShadows.EVSM, 8, 0);

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

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

    FOceanSimulation := TOceanSimulation.Create(FDevice);
    FSpatialFog := TSpatialFog.Create(FDevice);
    FTimer := TTimer.Create;
    FViewport := FObjects.CreateView;

    FCamera := TActorCamera.Create(Vector3f(0.0, 440.0, 0.0), Vector3f(0.0, Pi * -0.5, 0.0), 0.0);

    CreateAndLoadMeshes;
    CreateLightsAndMaterials;
    CreateObjects;

    FApplication.TryCaptureMouseInput;
  except
    on E: Exception do
    begin
      DisplayErrorMessage(E.Message);
      Exit(False);
    end;
  end;
  Result := True;
end;

procedure TApplicationHelper.AppDestroy(const AApplication: TApplication);
begin
  ReleaseObjectLightTracking;

  FCamera.Free;
  FTimer.Free;
  FSpatialFog.Free;
  FOceanSimulation.Free;
  FSceneModeling.Free;
  FSceneDepthNormals.Free;
  FShadowCastingAtlas.Free;
  FTextureCabinet.Free;
  FMaterials.Free;
  FLights.Free;
  FObjects.Free;
  FMeshes.Free;
  FTextRenderer.Free;
  FCanvas.Free;
  FDevice.Free;
end;

procedure TApplicationHelper.AppResize(const AApplication: TApplication);
begin
  try
    FDisplaySize := FApplication.ClientRect.Size;
    FSwapChain.Resize(FDisplaySize);
    FTextureCabinet.Size := FDisplaySize;
  except
    on E: Exception do
    begin
      DisplayErrorMessage(E.Message);
      FApplication.Terminate;
    end;
  end;
end;

procedure TApplicationHelper.AppRender(const AApplication: TApplication);
const
  FarPlane: Single = 8000.0;
  FogFade: Single = 0.15;
  CrossHairColor = $80FFFFFF;
var
  LToneMappingBloom: TToneMappingBloom;
  LAmbientOcclusion: TAmbientOcclusionParameters;
  LShadowParameters: TShadowParameters;
  LCrossHairInside, LCrossHairOutside, LCrossHairInsideMask, LCrossHairOutsideMask: Single;
  LColorInside, LColorOutside: TColorPair;
  LDepthClipNegative, LExcludeVehicles: Boolean;
  LCabinetAttributes: TTextureCabinetAttributes;
  I, LLightQueueProcessed: Integer;
  LAttributes: TSceneAttributes;
  LCasterToFilter: TShadowCaster;
  LOceanMaterial: TOceanMaterial;
  LFogParameters: TFogParameters;
  LParameters: TFontParameters;
  LTechString: string;
  LTracking: PObjectLightTracking;
  LPosition: TVector3f;
  LLatency: Double;
  LCenter: TPoint2f;
  LLight: PSceneLight;
  LMesh: TSceneMesh;
  LView: TMatrix4f;
begin
  try
    LDepthClipNegative := FDevice.Behavior and TDeviceBehavior.DepthClipNegative <> 0;
    FDisplayScale := FApplication.WindowScale;
    LLatency := FTimer.Latency / (Double(1000.0) * 1000.0);
    FDrawCalls := 0;

    // Perform animation of the ocean water simulation.
    FOceanSimulation.Update(LLatency);

    // Prepare view and projection matrices.
    LView := FCamera.View;
    FProjection := TMatrix4f.PerspectiveFOVY(Pi * 0.25, FDisplaySize.X / FDisplaySize.Y, 4.0, FarPlane,
      LDepthClipNegative);

    // Set view/projection matrices and update visible objects.
    FViewport.View := LView;
    FViewport.SetProjection(FProjection, LDepthClipNegative);
    FViewport.Update;

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

    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;

    // Adjust tone-mapping bloom parameters for HDR rendering.
    LToneMappingBloom := FTextureCabinet.ToneMappingBloom;
    LToneMappingBloom.ToneWhite := 2.0;
    LToneMappingBloom.BloomThreshold := 1.5;
    FTextureCabinet.ToneMappingBloom := LToneMappingBloom;

    // Adjust ambient occlusion parameters.
    LAmbientOcclusion := FTextureCabinet.AmbientOcclusion;
    LAmbientOcclusion.Radius := 4.0;
    LAmbientOcclusion.KernelBias := 0.05;
    FTextureCabinet.AmbientOcclusion := LAmbientOcclusion;

    // Prepare scene's attributes.
    LAttributes := [
      TSceneAttribute.Normals,
      TSceneAttribute.Coloring,
      TSceneAttribute.ShadowsCubic,
      TSceneAttribute.DepthPrePass,
      TSceneAttribute.LinearDepths];

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

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

    // Perform light assignment for 3D scene.
    FLights.ViewSize := FDisplaySize;
    FLights.ClustersCullingMode := TClustersCullingMode.Quality;
    FLights.ClusterSize := Round(32.0 * FDisplayScale);
    FLights.DepthSlices := 24;
    FLights.Execute(LView, FProjection);

    // Render shadow maps.
    FShadowMapsUpdated := 0;

    LShadowParameters := FShadowCastingAtlas.Parameters;
    LShadowParameters.BlurSigma := 0.75; // Shadow maps are small, so use less blur.
    LShadowParameters.Variance := 0.3e-5; // Compensate for wider viewing angles in shadow projections.
    FShadowCastingAtlas.Parameters := LShadowParameters;

    if not FShadowCastingAtlasPrepared then
    begin // Fill shadow casting atlas with border color only after startup.
      FShadowCastingAtlas.BorderFill;
      FShadowCastingAtlasPrepared := True;
    end;

    LCasterToFilter := TShadowCaster(NullObject);
    LLightQueueProcessed := -1;

    // Update shadow maps for all lights that have "update needed" attribute set in the associated object.
    // Note: in case of headlights, two lights refer to the same "tracking" object.
    for I := 0 to FLights.Count - 1 do
    begin
      LLight := FLights[I];

      LTracking := TObjectModel(LLight.Payload).Payload;
      if LTracking = nil then
        Continue;

      if (LLightQueueProcessed = -1) and (TLightTrackingAttribute.UpdateQueued in LTracking.Attributes) then
        LLightQueueProcessed := I;

      if (LLightQueueProcessed = I) or ((TLightTrackingAttribute.UpdateNeeded in LTracking.Attributes) or
        (TLightTrackingAttribute.LateUpdateNeeded in LTracking.Attributes)) then
      begin
        LTracking.View.Update;
        LTracking.Caster.ViewProjection := LTracking.View.ViewProjection;
        LExcludeVehicles := not (TLightTrackingAttribute.UpdateNeeded in LTracking.Attributes);

        if LCasterToFilter.Handle <> nil then
        begin // Filter previous caster before continuing with the current one.
          LCasterToFilter.Filter;
          LCasterToFilter := TShadowCaster(NullObject);
        end;

        LTracking.Caster.Clear;
        LTracking.Caster.BeginScene;
        try
          FSceneDepthNormals.Attributes := [];
          FSceneDepthNormals.View := LTracking.View.View;
          FSceneDepthNormals.Projection := LTracking.View.Projection;

          FSceneDepthNormals.BeginScene;
          try
            RenderObjects(FSceneDepthNormals, LTracking.View, LExcludeVehicles);
          finally
            FSceneDepthNormals.EndScene;
          end;
        finally
          LTracking.Caster.EndScene;
        end;
        Inc(FShadowMapsUpdated);

        // Delay filtering of shadow caster to enable better paralellism between CPU and GPU.
        LCasterToFilter := LTracking.Caster;

        // Remove "update needed" bit and if such doesn't exist, remove "late update needed" instead.
        if TLightTrackingAttribute.UpdateNeeded in LTracking.Attributes then
          LTracking.Attributes := LTracking.Attributes - [TLightTrackingAttribute.UpdateNeeded]
        else
          LTracking.Attributes := LTracking.Attributes - [TLightTrackingAttribute.LateUpdateNeeded];

        // If the shadow map was waiting in queue to be processed, remove its "queue" bit.
        LTracking.Attributes := LTracking.Attributes - [TLightTrackingAttribute.UpdateQueued];
      end;
    end;
    if LCasterToFilter.Handle <> nil then
      LCasterToFilter.Filter;

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

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

      FSceneModeling.Prepare;
      FSceneModeling.BeginScene;
      try
        RenderObjects(FSceneModeling, FViewport);
      finally
        FSceneModeling.EndScene;
      end;

      // Update ocean's material.
      LOceanMaterial := FOceanSimulation.Material;
      LOceanMaterial.Extinction := 0.008;
      FOceanSimulation.Material := LOceanMaterial;

      // Configure ocean's appearance.
      FOceanSimulation.Plane := Vector4f(0.0, 1.0, 0.0, -200.0);
      FOceanSimulation.Sections := Point2i((1280 * 3) div 2, (720 * 3) div 2);
      FOceanSimulation.View := LView;
      FOceanSimulation.Projection := FProjection;

      // Render the ocean.
      FOceanSimulation.Render(FTextureCabinet.Texture[TTextureCabinetType.LinearDepths], FLights,
        TShadowCastingAtlas(NullObject));
    finally
      FTextureCabinet.EndScene;
    end;

    // Perform object and camera movement.
    UpdateCameraMovement(LLatency);
    UpdateVehicleMovement(LLatency);
    UpdateBridges;
    SynchronizeLights;

    // Render a plane to generate linear depths right above ocean for proper fog simulation.
    // This has to be performed AFTER ocean simulation has been rendered, since it'll override ocean depths.
    FTextureCabinet.BeginScene(TTextureCabinetPass.Depth);
    try
      LPosition := FCamera.Position;

      FSceneDepthNormals.Attributes := LAttributes;
      FSceneDepthNormals.World := TMatrix4f.Scale(32000.0, 1.0, 32000.0) *
        TMatrix4f.Translate(LPosition.X, 215.0, LPosition.Z);
      FSceneDepthNormals.View := LView;
      FSceneDepthNormals.Projection := FProjection;

      FSceneDepthNormals.BeginScene;
      try
        LMesh := FMeshes.Mesh['Plane'];
        LMesh.Model.Draw(FSceneDepthNormals.SceneProgram);
      finally
        FSceneDepthNormals.EndScene;
      end;
    finally
      FTextureCabinet.EndScene;
    end;
    // Apply fog.
    LFogParameters := TFogParameters.Default;
    LFogParameters.Color := FloatColor(0.011765 * FogFade, 0.333333 * FogFade, 0.470588 * FogFade, 0.0);
    LFogParameters.Opacity := 1.0;
    LFogParameters.GroundPlane := Vector4f(0.0, 1.0, 0.0, -600.0);
    LFogParameters.Scattering := 1.0;
    LFogParameters.Extinction := 0.0003;
    LFogParameters.DensityGround := 0.03;
    FSpatialFog.Parameters := LFogParameters;

    FSpatialFog.Formula := TFogFormula.Exponential;
    FSpatialFog.Distance := TDepthFogDistance.EyeRelative;
    FSpatialFog.View := LView;
    FSpatialFog.Projection := FProjection;

    FSpatialFog.Execute(FTextureCabinet.Texture[TTextureCabinetType.ColorHDR],
      FTextureCabinet.Texture[TTextureCabinetType.LinearDepths]);

    FTextureCabinet.Filter(TTextureCabinetFilterType.Bloom);

    // Render on the form.
    FSwapChain.BeginScene;
    try
      FDevice.Clear([TClearLayer.Color], FloatColor($FF404040));
      FTextureCabinet.Present;

      FCanvas.BeginScene;
      try
        FCanvas.ContextState := TCanvasContextState.FlatScene;

        // Draw a crosshair to know where the camera is pointing at.
        LCrossHairInside := 8.0 * FDisplayScale;
        LCrossHairOutside := 12.0 * FDisplayScale;

        LCrossHairInsideMask := 8.0 * FDisplayScale - 1.0;
        LCrossHairOutsideMask := 12.0 * FDisplayScale + 1.0;

        LCenter := TPoint2f(FDisplaySize) * 0.5;
        LColorInside := ColorPair(MakeColor(CrossHairColor, 0), CrossHairColor);
        LColorOutside := ColorPair(CrossHairColor, MakeColor(CrossHairColor, 0));

        FCanvas.Ribbon(LCenter, Point2f(LCrossHairInside, LCrossHairInside), Point2f(LCrossHairOutside,
          LCrossHairOutside), 32, CrossHairColor);

        // Simulate anti-aliasing by rendering 1-pixel secondary ribbons inside and outside of the main
        // ribbon.
        FCanvas.Ribbon(LCenter, Point2f(LCrossHairInsideMask, LCrossHairInsideMask),
          Point2f(LCrossHairInside, LCrossHairInside), 32, LColorInside, LColorInside, LColorInside);

        FCanvas.Ribbon(LCenter, Point2f(LCrossHairOutside, LCrossHairOutside), Point2f(LCrossHairOutsideMask,
          LCrossHairOutsideMask), 32, LColorOutside, LColorOutside, LColorOutside);

        // Render information text.
        FCanvas.ContextState := TCanvasContextState.FlatText;
        FCanvas.SamplerState := TCanvasSamplerState.Default;

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

        FTextRenderer.DrawAligned(Point2f(FDisplaySize.X * 0.5, 4.0 * FDisplayScale),
          'W/S keys - move forward and backward, A/D keys - move left and right.',
          ColorPair($FFA5E7B8, $FF77FFCE), TTextAlignment.Middle, TTextAlignment.Start);

        FTextRenderer.DrawAligned(Point2f(FDisplaySize.X * 0.5, FDisplaySize.Y - 4.0 * FDisplayScale),
          'Mouse movement - rotate scene, Space/C keys - move up and down.',
          ColorPair($FFA5E7B8, $FF77FFCE), TTextAlignment.Middle, TTextAlignment.Ending);

        FTextRenderer.DrawAligned(Point2f(8.0 * FDisplayScale, 4.0 * FDisplayScale),
          Utf8String('Frame Rate: ' + IntToStr(Round(FTimer.FrameRate))),
          ColorPair($A0FFFFFF), TTextAlignment.Start, TTextAlignment.Start);

        FTextRenderer.DrawAligned(Point2f(8.0 * FDisplayScale, 32.0 * FDisplayScale),
          Utf8String('Lights: ' + IntToStr(FLights.Count) + ', shadow maps updated: ' +
          IntToStr(FShadowMapsUpdated)), ColorPair($A0FFFFFF), TTextAlignment.Start, TTextAlignment.Start);

        FTextRenderer.DrawAligned(Point2f(FDisplaySize.X - 8.0 * FDisplayScale, 32.0 * FDisplayScale),
          Utf8String('Objects visible: ' + IntToStr(FObjectsVisible) + ' (out of ' +
          IntToStr(FObjects.ObjectCount) + '), draw calls: ' + IntToStr(FDrawCalls)), ColorPair($A0FFFFFF),
          TTextAlignment.Ending, TTextAlignment.Start);

        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.DrawAligned(Point2f(FDisplaySize.X - 12.0 * FDisplayScale, 4.0 * FDisplayScale),
          LTechString, ColorPair($A0FFFFFF), TTextAlignment.Ending, TTextAlignment.Start);
      finally
        FCanvas.EndScene;
      end;
    finally
      FSwapChain.EndScene;
    end;
    FTimer.Update;
  except
    on E: Exception do
    begin
      DisplayErrorMessage(E.Message);
      FApplication.Terminate;
    end;
  end;
end;

procedure TApplicationHelper.AppMouse(const AApplication: TApplication; const AEvent: TMouseEvent;
  const AButton: TMouseButton; const APosition: TPoint2i);
const
  Sensitivity = 0.002;
  Alpha = 0.6;
var
  LSmoothMouse: TPoint2f;
  LRotation: TVector4f;
begin
  if (AEvent = TMouseEvent.MouseMove) and (not FMinimized) and FActive then
  begin
    if (not IsNan(FLastSmoothMouse.X)) and (not IsNan(FLastSmoothMouse.Y)) then
      LSmoothMouse := TPoint2f(APosition).Lerp(FLastSmoothMouse, Alpha)
    else
      LSmoothMouse := APosition;

    FLastSmoothMouse := LSmoothMouse;
    LRotation := FCamera.Rotation;

    LRotation.X := LRotation.X + LSmoothMouse.Y * Sensitivity;
    LRotation.Y := LRotation.Y + LSmoothMouse.X * Sensitivity;

    FCamera.Rotation := LRotation;
  end;
end;

procedure TApplicationHelper.AppKey(const AApplication: TApplication; const AEvent: TKeyEvent;
  const AVirtualKey: Integer; const AKeyCode: Word);
var
  LKey: TKey;
begin
  LKey := FApplication.TranslateVirtualKey(AVirtualKey);

  if (LKey = TKey.AltLeft) or (LKey = TKey.AltRight) then
  begin
    if AEvent = TKeyEvent.Pressed then
      Inc(FKeyAlt)
    else if AEvent = TKeyEvent.Released then
      Dec(FKeyAlt);
  end
  else if (LKey = TKey.Escape) and (AEvent = TKeyEvent.Pressed) then
    FApplication.Terminate
  else if (LKey = TKey.Return) and (AEvent = TKeyEvent.Pressed) and (FKeyAlt > 0) then
  begin
    if FApplication.WindowState <> TApplicationWindowState.FullScreen then
      FApplication.WindowState := TApplicationWindowState.FullScreen
    else
      FApplication.WindowState := TApplicationWindowState.Windowed;
  end
  else if (Ord(LKey) = Ord('w')) or (LKey = TKey.Up) then
  begin
    if AEvent = TKeyEvent.Pressed then
      Include(FMovementDirection, TMovementDirection.Forward)
    else if AEvent = TKeyEvent.Released then
      Exclude(FMovementDirection, TMovementDirection.Forward);
  end
  else if (Ord(LKey) = Ord('s')) or (LKey = TKey.Down) then
  begin
    if AEvent = TKeyEvent.Pressed then
      Include(FMovementDirection, TMovementDirection.Backward)
    else if AEvent = TKeyEvent.Released then
      Exclude(FMovementDirection, TMovementDirection.Backward);
  end
  else if (Ord(LKey) = Ord('a')) or (LKey = TKey.Left) then
  begin
    if AEvent = TKeyEvent.Pressed then
      Include(FMovementDirection, TMovementDirection.Left)
    else if AEvent = TKeyEvent.Released then
      Exclude(FMovementDirection, TMovementDirection.Left);
  end
  else if (Ord(LKey) = Ord('d')) or (LKey = TKey.Right) then
  begin
    if AEvent = TKeyEvent.Pressed then
      Include(FMovementDirection, TMovementDirection.Right)
    else if AEvent = TKeyEvent.Released then
      Exclude(FMovementDirection, TMovementDirection.Right);
  end
  else if Ord(LKey) = Ord('c') then
  begin
    if AEvent = TKeyEvent.Pressed then
      Include(FMovementDirection, TMovementDirection.Down)
    else if AEvent = TKeyEvent.Released then
      Exclude(FMovementDirection, TMovementDirection.Down);
  end
  else if LKey = TKey.Space then
  begin
    if AEvent = TKeyEvent.Pressed then
      Include(FMovementDirection, TMovementDirection.Up)
    else if AEvent = TKeyEvent.Released then
      Exclude(FMovementDirection, TMovementDirection.Up);
  end;
end;

procedure TApplicationHelper.AppStatus(const AApplication: TApplication; const AEvent: TApplicationEvent);
begin
  case AEvent of
    TApplicationEvent.Activate:
      begin
        if not FApplication.MouseInputCaptured then
          FApplication.TryCaptureMouseInput;
        FActive := True;
      end;

    TApplicationEvent.Deactivate:
      begin
        if FApplication.MouseInputCaptured then
          FApplication.TryReleaseMouseInput;
        FActive := False;
      end;

    TApplicationEvent.Restore:
      FMinimized := False;

    TApplicationEvent.Minimize:
      FMinimized := True;
  end;
end;

function TApplicationHelper.AppIdle(const AApplication: TApplication): Boolean;
begin
  if FActive and (not FMinimized) then
    FApplication.Invalidate;
  Result := False;
end;

function TApplicationHelper.Execute: Boolean;
begin
  Result := True;
  try
    FApplication.Execute;
  except
    Result := False;
  end;
end;

procedure TApplicationHelper.RenderObjects(const AScene: TScene; const AView: TObjectModelView;
  const AExcludeVehicles: Boolean);
var
  LDepthClipNegative, LMultisampling: Boolean;
  LRenderingState, LInitialState: TRenderingState;
  LOptions: Cardinal;
begin
  LDepthClipNegative := FDevice.Behavior and TDeviceBehavior.DepthClipNegative <> 0;
  LMultisampling := FTextureCabinet.Samples > 1;

  // Draw all objects, skipping portions of objects that are semi-transparent. Only vehicles have their
  // windshields semi-transparent, so this will draw vehicles without their windshields, the last of which
  // will be rendered separately below.
  LOptions := TAutoDrawOption.MaterialSkipTransparent;

  if AExcludeVehicles then
    LOptions := LOptions or TAutoDrawOption.ObjectSkipTransparent;

  AView.AutoDraw(AScene, FMaterials, LOptions, FDrawCalls);

  if (not AExcludeVehicles) and (TSceneAttribute.Modeling in AScene.Attributes) then
  begin
    FObjectsVisible := AView.ObjectCount;

    // Retrieve current rendering state.
    LInitialState := FDevice.RenderingState;

    // Simulate some sort of transparency without resorting to more complex techniques by using alpha-
    // blending and alpha-to-coverage mode. As this is done during HDR rendering, it would be inaccurate.
    // However, vehicle windshield is a minor detail, so simplicity is a reasonable compromise.

    LRenderingState := LInitialState;
    LRenderingState.States := LRenderingState.States or TRenderingState.State.BlendEnable;
    LRenderingState.BlendColor.Source := TBlendFactor.One;
    LRenderingState.BlendColor.Dest := TBlendFactor.Zero;
    // Destination alpha-channel actually contains bloom factors, so reduce it based on source alpha values.
    LRenderingState.BlendAlpha.Source := TBlendFactor.Zero;
    LRenderingState.BlendAlpha.Dest := TBlendFactor.InvSourceAlpha;

    if not LMultisampling then
    begin
      LRenderingState.BlendColor.Source := TBlendFactor.SourceAlpha;
      LRenderingState.BlendColor.Dest := TBlendFactor.InvSourceAlpha;
    end
    else
      LRenderingState.States := LRenderingState.States or TRenderingState.State.AlphaToCoverage;

    // Depth clip negative behavior only occurs under older OpenGL implementations that do not support clip
    // control either under normal API or through extension. It has significantly less depth precision.
    if LDepthClipNegative then
      LRenderingState.DepthFunc := TComparisonFunc.LessEqual
    else
      LRenderingState.DepthFunc := TComparisonFunc.GreaterEqual;

    FDevice.RenderingState := LRenderingState;

    // Draw all vehicles windshields separately. Note that during creation, vehicles were marked with "transparent"
    // attribute. The following function is instructed to omit all objects that do not have this attribute
    // set, so it would quickly skip all objects that aren't vehicles.
    AView.AutoDraw(AScene, FMaterials, TAutoDrawOption.ObjectSkipNonTransparent or
      TAutoDrawOption.MaterialTransparency or TAutoDrawOption.MaterialSkipOpaque, FDrawCalls);

    // Restore previous rendering state.
    FDevice.RenderingState := LInitialState;
  end;
end;

procedure TApplicationHelper.UpdateVehicleMovement(const ALatency: Double);
var
  LVehicleIndex, LSide, LLamppostBest, LDirection, LDirectionSign, LLightIndex, I: Integer;
  LCameraPosition, LDimensions, LPosition, LPositionView: TVector3f;
  LVehicleLength, LVelocity, LDistance, LDistanceBest: Single;
  LTrackingVehicle, LTracking: PObjectLightTracking;
  LTransform, LView: TMatrix4f;
  LObject: TObjectModel;
  LLight: PSceneLight;
begin
  LView := FCamera.View;
  LCameraPosition := FCamera.Position;
  LVehicleLength := 0.0;

  for LVehicleIndex := Low(FVehicles) to High(FVehicles) do
  begin
    LObject := FVehicles[LVehicleIndex];

    LTrackingVehicle := LObject.Payload;
    if LTrackingVehicle = nil then
      Continue;

    if LVehicleLength < VectorEpsilon then
    begin // Determine vehicle maximum dimensions.
      LDimensions := LObject.Size;
      LVehicleLength := Max(Max(LDimensions.X, LDimensions.Y), LDimensions.Z);
    end;

    // Retrieve vehicle's global 3D position.
    LTransform := LObject.Transform[TModelTransform.Global];
    LPosition := LTransform.WorldPos;

    // Retrieve vehicle's local 3D position to move it around the bridges.
    LTransform := LObject.Transform[TModelTransform.Local];
    LVelocity := LTransform.Data[0, 2];

    LTransform.Data[3, 0] := LTransform.Data[3, 0] + ALatency * VehicleMovementSpeed * LVelocity;

    if LTransform.Data[3, 0] < -FBridgeSize.X * 0.5 then
    begin
      LTrackingVehicle.Movement.Bridge := (LTrackingVehicle.Movement.Bridge +
        BridgeCount - 1) mod BridgeCount;
      LObject.Parent := FBridges[LTrackingVehicle.Movement.Bridge];
      LTransform.Data[3, 0] := LTransform.Data[3, 0] + FBridgeSize.X;
    end
    else if LTransform.Data[3, 0] > FBridgeSize.X * 0.5 then
    begin
      LTrackingVehicle.Movement.Bridge := (LTrackingVehicle.Movement.Bridge + 1) mod BridgeCount;
      LObject.Parent := FBridges[LTrackingVehicle.Movement.Bridge];
      LTransform.Data[3, 0] := LTransform.Data[3, 0] - FBridgeSize.X;
    end;

    LObject.Transform[TModelTransform.Local] := LTransform;

    // Calculate vehicle position in "view" space to determine if it is in front or behind the camera.
    LPositionView := LPosition * LView;

    // Calculate 2D distance between the vehicle and camera position.
    LDistance := LCameraPosition.GetXY.Distance(LPosition.GetXY);

    // If the vehicle is far behind the camera, or too far in front of the camera, skip shadow map updates.
    if ((LPositionView.Z < 0.0) and (LDistance > 500.0)) or (LDistance > 4000.0) then
      Continue; // Do not refresh shadow maps if vehicle is far behind the camera.

    // Mark the headlights to have their shadow maps updated.
    LTrackingVehicle.Attributes := LTrackingVehicle.Attributes + [TLightTrackingAttribute.UpdateNeeded];

    // Determine nearby lampposts to update their shadow maps.
    for LSide := 0 to 1 do
    begin
      LDistanceBest := MaxSingle;
      LLamppostBest := -1;

      // Search in both directions to determine what lights may have the vehicle in their shadow caster view.
      for LDirection := 0 to 1 do
      begin
        LDirectionSign := (LDirection * 2) - 1;

        for I := LDirection to LightsPerSide - 1 do
        begin
          LLightIndex := (LTrackingVehicle.Movement.Lampposts[LSide] + (I * LDirectionSign) +
            LightsPerSide) mod LightsPerSide;
          LLight := FLamppostLights[LSide, LLightIndex];

          LTracking := TObjectModel(LLight.Payload).Payload;
          if (LTracking = nil) or (TLightTrackingAttribute.Headlight in LTracking.Attributes) then
            Continue;

          LDistance := LLight.Position.Distance(LPosition);
          if LDistance < LLight.AttenuationEnd + LVehicleLength then
          begin
            LTracking.Attributes := LTracking.Attributes + [TLightTrackingAttribute.UpdateNeeded,
              TLightTrackingAttribute.LateUpdateNeeded];

            if LDistance < LDistanceBest then
            begin
              LDistanceBest := LDistance;
              LLamppostBest := LLightIndex;
            end;
          end
          else if LLamppostBest <> -1 then
            Break;
        end;
        LTrackingVehicle.Movement.Lampposts[LSide] := LLamppostBest;
      end;
    end;
  end;
end;

procedure TApplicationHelper.UpdateCameraMovement(const ALatency: Double);
var
  LDirection, LPosition: TVector3f;
  LAcceleration, LMagnitude, LMagnitudeNew: Single;
  LLight: PSceneLight;
begin
  LDirection := TVector3f.Zero;

  if TMovementDirection.Forward in FMovementDirection then
    LDirection := LDirection + FCamera.Forward
  else if TMovementDirection.Backward in FMovementDirection then
    LDirection := LDirection - FCamera.Forward;

  if TMovementDirection.Left in FMovementDirection then
    LDirection := LDirection - FCamera.Right
  else if TMovementDirection.Right in FMovementDirection then
    LDirection := LDirection + FCamera.Right;

  if TMovementDirection.Up in FMovementDirection then
    LDirection := LDirection + FCamera.Ceiling
  else if TMovementDirection.Down in FMovementDirection then
    LDirection := LDirection - FCamera.Ceiling;

  LAcceleration := ALatency * CameraAcceleration;

  if not LDirection.Empty then
  begin
    LDirection := LDirection.Normalize * LAcceleration;
    FVelocity := FVelocity + LDirection;

    LMagnitude := FVelocity.Length;
    if LMagnitude > CameraMovementSpeed then
      FVelocity := FVelocity * (CameraMovementSpeed / LMagnitude);
  end
  else
  begin
    LMagnitude := FVelocity.Length;
    if LMagnitude > VectorEpsilon then
    begin
      LMagnitudeNew := Max(LMagnitude - LAcceleration, 0.0);
      FVelocity := FVelocity * (LMagnitudeNew / LMagnitude);
    end;
  end;

  if not FVelocity.Empty then
  begin
    LPosition := FCamera.Position + FVelocity * ALatency;
    if LPosition.Y < CameraMinimumAltitude then
      LPosition.Y := CameraMinimumAltitude;

    FCamera.Position := LPosition;

    LLight := FLights[0];
    LLight.Position.X := LightPosition.X + LPosition.X;
  end;
end;

procedure TApplicationHelper.UpdateBridges(const AStartup: Boolean);
var
  LBridgeIndex: Integer;
  LCameraPos: TVector3f;
  LTransform: TMatrix4f;
  LPosition: Single;
  LObject: TObjectModel;
begin
  LCameraPos := FCamera.Position;

  for LBridgeIndex := Low(FBridges) to High(FBridges) do
  begin
    LObject := FBridges[LBridgeIndex];

    LTransform := LObject.Transform[TModelTransform.Local];
    LPosition := LTransform.Data[3, 0] - LCameraPos.X;

    if ((FVelocity.X < -VectorEpsilon) or AStartup) and (LPosition > FBridgeLength * BridgeCount * 0.5) then
    begin
      LTransform.Data[3, 0] := LTransform.Data[3, 0] - FBridgeLength * BridgeCount;
      LObject.Transform[TModelTransform.Local] := LTransform;
    end
    else if ((FVelocity.X > VectorEpsilon) or AStartup) and
      (LPosition < -FBridgeLength * BridgeCount * 0.5) then
    begin
      LTransform.Data[3, 0] := LTransform.Data[3, 0] + FBridgeLength * BridgeCount;
      LObject.Transform[TModelTransform.Local] := LTransform;
    end;
  end;
end;

procedure TApplicationHelper.SynchronizeLights;
var
  LDepthClipNegative: Boolean;
  LTransform: TMatrix4f;
  LTracking: PObjectLightTracking;
  LPosition: TVector3f;
  LObject: TObjectModel;
  LLight: PSceneLight;
  I, LHeadlightIndex: Integer;
begin
  LDepthClipNegative := FDevice.Behavior and TDeviceBehavior.DepthClipNegative <> 0;

  for I := 0 to FLights.Count - 1 do
  begin
    LLight := FLights[I];
    LObject := TObjectModel(LLight.Payload);

    LTracking := LObject.Payload;
    if LTracking = nil then
      Continue;

    if TLightTrackingAttribute.Headlight in LTracking.Attributes then
    begin // Headlight
      LHeadlightIndex := I mod 2; // Headlights are created in pairs.

      LTransform := LObject.GetLatchTransform(LHeadlightIndex);

      LLight.Position := LTransform.WorldPos;
      LLight.Direction := (Vector4f(0.0, -0.25, -1.0, 0.0) * LTransform).GetXYZ.Normalize;

      if LHeadlightIndex = 0 then
      begin // There is only one shadow caster per two headlights.
        LTransform := LObject.GetLatchTransform(LHeadlightIndex xor 1);
        LPosition := (LLight.Position + LTransform.WorldPos) * 0.5;

        LTracking.View.View := TMatrix4f.LookAt(LPosition, LPosition + LLight.Direction, TVector3f.AxisY);
        LTracking.View.SetProjection(TMatrix4f.PerspectiveFOVY(Pi * 0.55, 1.0, 5.0, 300.0,
          LDepthClipNegative), LDepthClipNegative);
      end;
    end
    else
    begin // Lamppost
      LTransform := LObject.Transform[TModelTransform.Global];
      LPosition := (TMatrix4f.Translate(-2.85, 24.4, 0.0) * LTransform).WorldPos;

      if LLight.Position <> LPosition then
        LTracking.Attributes := LTracking.Attributes + [TLightTrackingAttribute.UpdateQueued];

      LLight.Position := LPosition;

      LTracking.View.View := TMatrix4f.LookAt(LLight.Position, LLight.Position + LLight.Direction,
        -TVector3f.AxisZ);
      LTracking.View.SetProjection(TMatrix4f.PerspectiveFOVY(Pi * 0.68, 1.0, 10.0, 60.0,
        LDepthClipNegative), LDepthClipNegative);
    end;
  end;
end;

procedure TApplicationHelper.CreateBridgeLampposts(const ABridge: TObjectModel; var ASideLights: TSideLights);
var
  LTheta: Single;
  LLamppost: TObjectModel;
  LSide, I: Integer;
  LTracking: PObjectLightTracking;
  LLightColor: TFloatColorRGB;
  LLight: PSceneLight;
begin
  for I := 0 to LamppostCount - 1 do
  begin
    LTheta := (I / LamppostCount) - 0.5;

    for LSide := 0 to 1 do
    begin
      // Create light that will be associated with the lamppost.
      LLight := FLights.Add;

      if (((I - LSide) mod 3) <> 2) xor (LSide <> 0) then
        LLightColor := FloatColorRGB(1.0, 0.95, 0.85)
      else
        LLightColor := FloatColorRGB(0.85, 0.95, 1.0);

      LLight.AmbientColor := LLightColor;
      LLight.AlbedoColor := LLightColor;
      LLight.SpecularColor := TFloatColorRGB.White;
      LLight.SpecularExponent := 1.0;
      LLight.Intensity := 1.0;
      LLight.Direction := (Vector4f(0.0, -1.0, 0.0, 0.0) * TMatrix4f.RotateX(-Pi * 0.15 *
        (1 - LSide * 2))).GetXYZ.Normalize;
      LLight.AttenuationEnd := 80.0;
      LLight.AttenuationStart := 40.0;
      LLight.AngleCutoff := Pi * 0.4;
      LLight.Angle := LLight.AngleCutoff * 0.45;
      LLight.ShadowCaster := FShadowCastingAtlas.CasterCount;

      // Assign light to cyclic array for fast searching.
      FLamppostLights[LSide, ASideLights[LSide]] := LLight;

      // Create lamppost's payload.
      New(LTracking);
      FillChar(LTracking^, SizeOf(TObjectLightTracking), 0);

      LTracking.Light := LLight;
      LTracking.Caster := FShadowCastingAtlas.Add(ShadowMapSize);
      LTracking.View := FObjects.CreateView;
      LTracking.Attributes := [
        TLightTrackingAttribute.UpdateNeeded,
        TLightTrackingAttribute.LateUpdateNeeded];

      // Create the lampost itself.
      LLamppost := FObjects.Add('', LTracking);
      LLamppost.MeshName := 'Lamppost';
      LLamppost.Parent := ABridge;
      LLamppost.Material := 0;

      LLamppost.Transform[TModelTransform.Local] := TMatrix4f.RotateY(Pi * (0.5 + LSide)) *
        TMatrix4f.Translate(FBridgeLength * LTheta + (LSide * (FBridgeLength / LamppostCount) * 0.5),
        BridgeAt, ((LSide * 2) - 1) * 37.0);

      LLight.Payload:= LLamppost.Handle;

      Inc(ASideLights[LSide]);
    end;
  end;
end;

procedure TApplicationHelper.CreateBridgeObstacles(const ABridge: TObjectModel; var APRNG: TRandomSequence);
const
  MeshNames: array[0..2] of Utf8String = ('Roadblock2', 'TrafficDrum', 'Roadblock');
var
  LSide, LMeshIndex, LSign: Integer;
  LObjectSize: TVector3f;
  LPosition: Single;
  LObject: TObjectModel;
begin
  for LSide := 0 to 1 do
  begin
    LMeshIndex := -1;
    LPosition := -FBridgeSize.X * 0.5;

    while (LPosition < FBridgeSize.X * 0.5) do
    begin
      LSign := LSide * 2 - 1;

      if LMeshIndex <> 2 then
        LMeshIndex := APRNG.Ranged(3)
      else
        LMeshIndex := APRNG.Ranged(2);

      LObject := FObjects.Add;
      LObject.MeshName := MeshNames[LMeshIndex];
      LObject.Parent := ABridge;
      LObject.Material := 2;

      LObjectSize := LObject.Size;
      LPosition := LPosition + LObjectSize.X * 0.5;

      if LMeshIndex = 2 then
        LPosition := LPosition + 10.0 + APRNG.Value * 20.0;

      if LPosition + LObjectSize.X * 0.5 >= FBridgeSize.X * 0.5 then
        Break;

      if LMeshIndex = 2 then
      begin
        LObject.Transform[TModelTransform.Local] := TMatrix4f.RotateY((1 - LSide) * Pi) *
          TMatrix4f.Translate(LPosition, BridgeAt, LSign * 28.0);

        LObject := FObjects.Add;
        LObject.MeshName := MeshNames[LMeshIndex];
        LObject.Parent := ABridge;
        LObject.Material := 2;

        LObject.Transform[TModelTransform.Local] := TMatrix4f.RotateY((1 - LSide) * Pi) *
          TMatrix4f.Translate(LPosition, BridgeAt, LSign * 35.0);
      end
      else
        LObject.Transform[TModelTransform.Local] := TMatrix4f.Translate(LPosition, BridgeAt,
          LSign * (26.0 + APRNG.Value - 0.5));

      LPosition := LPosition + LObjectSize.X * 0.5 + 1.0;

      if LMeshIndex = 2 then
        LPosition := LPosition + 10.0 + APRNG.Value * 20.0
      else if APRNG.Ranged(2) = 1 then
        LPosition := LPosition + APRNG.Value * 40.0;
    end;
  end;
end;

procedure TApplicationHelper.CreateVehicles(var APRNG: TRandomSequence);
const
  ALightColor: TFloatColorRGB = (Red: 1.0; Green: 0.9; Blue: 0.65);
var
  LVehicleIndex, LFacing, LFacingSign, LShadowCasterIndex, I: Integer;
  LHeadlights: array[0..1] of PSceneLight;
  LTracking: PObjectLightTracking;
begin
  for LVehicleIndex := Low(FVehicles) to High(FVehicles) do
  begin
    LFacing := LVehicleIndex div VehicleCountHalf;
    LFacingSign := 1 - (LFacing * 2);

    // Create lights that will serve as vehicle's headlights.
    LShadowCasterIndex := FShadowCastingAtlas.CasterCount;

    for I := 0 to 1 do
    begin
      LHeadlights[I] := FLights.Add;

      LHeadlights[I].AmbientColor := ALightColor;
      LHeadlights[I].AlbedoColor := ALightColor;
      LHeadlights[I].SpecularColor := TFloatColorRGB.White;
      LHeadlights[I].SpecularExponent := 4.0;
      LHeadlights[I].Intensity := 1.0;
      LHeadlights[I].AttenuationEnd := 300.0;
      LHeadlights[I].AttenuationStart := 100.0;
      LHeadlights[I].AngleCutoff := Pi * 0.2;
      LHeadlights[I].Angle := LHeadlights[I].AngleCutoff * 0.45;
      LHeadlights[I].ShadowCaster := LShadowCasterIndex;
    end;

    // Create vehicle's payload.
    New(LTracking);
    FillChar(LTracking^, SizeOf(TObjectLightTracking), 0);

    New(LTracking.Movement);
    FillChar(LTracking.Movement^, SizeOf(TMovementTracking), 0);

    // Create vehicle's shadow caster and its view.
    LTracking.Caster := FShadowCastingAtlas.Add(ShadowMapSize);
    LTracking.View := FObjects.CreateView;
    LTracking.Attributes := [TLightTrackingAttribute.Headlight, TLightTrackingAttribute.UpdateNeeded];

    if BridgeCount <= 8 then
      LTracking.Movement.Bridge := (((LVehicleIndex mod VehicleCountHalf) * 2) + BridgeCount -
        LFacing) mod bridgeCount
    else
      LTracking.Movement.Bridge := LVehicleIndex mod BridgeCount;

    LTracking.Movement.Lampposts[0] := -1;
    LTracking.Movement.Lampposts[1] := -1;

    // Create the vehicle itself.
    FVehicles[LVehicleIndex] := FObjects.Add('', LTracking, [TModelAttribute.Visible,
      TModelAttribute.Selectable, TModelAttribute.Transparent, TModelAttribute.Hierarchyless]);

    FVehicles[LVehicleIndex].MeshName := 'Convertible';
    FVehicles[LVehicleIndex].Parent := FBridges[LTracking.Movement.Bridge];
    FVehicles[LVehicleIndex].Material := 1;

    FVehicles[LVehicleIndex].Transform[TModelTransform.Local] := TMatrix4f.RotateY(Pi * 0.5 * LFacingSign) *
      TMatrix4f.Translate((APRNG.Value - 0.5) * FBridgeSize.X, BridgeAt, 11.0 * LFacingSign);

    // Associate headlights with the vehicle for synchronization purposes.
    LHeadlights[0].Payload := FVehicles[LVehicleIndex].Handle;
    LHeadlights[1].Payload := FVehicles[LVehicleIndex].Handle;
  end;
end;

procedure TApplicationHelper.CreateObjects;
var
  LBridgeIndex: Integer;
  LBridge: TObjectModel;
  LSideLights: TSideLights;
  LPRNG: TRandomSequence;
begin
  FillChar(LSideLights, SizeOf(TSideLights), 0);
  LPRNG := TRandomSequence.Create;

  for LBridgeIndex := Low(FBridges) to High(FBridges) do
  begin
    LBridge := FObjects.Add('', nil, [TModelAttribute.Visible, TModelAttribute.Selectable,
      TModelAttribute.Hierarchyless]);
    LBridge.MeshName := 'Bridge';
    LBridge.Material := 0;

    if FBridgeSize.Empty then
    begin
      FBridgeSize := LBridge.Size;
      FBridgeLength := FBridgeSize.X - 0.3;
    end;

    LBridge.Transform[TModelTransform.Local] := TMatrix4f.Translate(FBridgeLength * LBridgeIndex, 0.0, 0.0);
    FBridges[LBridgeIndex] := LBridge;

    CreateBridgeLampposts(LBridge, LSideLights);
    CreateBridgeObstacles(LBridge, LPRNG);
  end;

  CreateVehicles(LPRNG);

  // Make sure to update bridge positions to be around the camera and synchronize light positions.
  UpdateBridges(True);
  UpdateCameraMovement(0.0);
  SynchronizeLights;
end;

procedure TApplicationHelper.CreateAndLoadMeshes;
const
  BasePath = '../../../../Media/Bridge/';
  MeshNames: array[0..5] of string = ('Bridge', 'Lamppost', 'Roadblock', 'Roadblock2', 'TrafficDrum',
    'Convertible');
  MeshScale: Single = 10.0;
var
  LMeshBuffer: TMeshBuffer;
  LOptions: Cardinal;
  I: Integer;
begin
  // Create geometric meshes.
  LMeshBuffer := TMeshBuffer.Create;
  try
    LMeshBuffer.Plane(4, 4, TVector3f.Zero, TVector3f.AxisX, TVector3f.AxisZ, TVector3f.AxisY,
      TPoint2f.Zero, TPoint2f.Unity, TFloatColor.White);

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

  // Load meshes from disk.
  for I := 0 to High(MeshNames) do
  begin
    LOptions := 0;
    FMeshes.AddFromFile(Utf8String(MeshNames[I]), Utf8String(BasePath + MeshNames[I] + '.mesh'), nil,
      VertexElements, 0, 0, LOptions, MeshScale);
  end;
end;

procedure TApplicationHelper.CreateLightsAndMaterials;
var
  LLight: PSceneLight;
  LMaterial: PObjectMaterial;
begin
  // (0) Light for water and bridge
  LLight := FLights.Add;
  LLight.AmbientColor := TFloatColorRGB.White;
  LLight.AlbedoColor := TFloatColorRGB.White;
  LLight.SpecularColor := TFloatColorRGB.White;
  LLight.SpecularExponent := 1.0;
  LLight.Intensity := 1.0;
  LLight.Position := LightPosition;
  LLight.Bitmask := $01;

  // (1) Ambient light for objects on the bridge
  LLight := FLights.Add;
  LLight.AmbientColor := TFloatColorRGB.White;
  LLight.AlbedoColor := TFloatColorRGB.Black;
  LLight.SpecularColor := TFloatColorRGB.Black;
  LLight.SpecularExponent := 1.0;
  LLight.Intensity := 1.0;
  LLight.Bitmask := $02;

  // (0) Bridge and Lampposts
  LMaterial := FMaterials.Add;
  LMaterial.AmbientColor := FloatColorRGB(0.15, 0.15, 0.15);
  LMaterial.AlbedoColor := FloatColor(1.0, 1.0, 1.0, 0.5);
  LMaterial.SpecularColor := FloatColorRGB(0.25, 0.25, 0.25);
  LMaterial.SpecularExponent := 16.0;
  LMaterial.Occlusion := 1.0;
  LMaterial.Roughness := Vector3f(0.9, 0.7, 0.2);
  LMaterial.Technique := TTechniqueLighting.CookTorrance;
  LMaterial.Bitmask := $01;

  // (1) Vehicles
  LMaterial := FMaterials.Add;
  LMaterial.AmbientColor := FloatColorRGB(0.2, 0.2, 0.2);
  LMaterial.AlbedoColor := FloatColor(1.0, 1.0, 1.0, 0.5);
  LMaterial.SpecularColor := TFloatColorRGB.White;
  LMaterial.SpecularExponent := 32.0;
  LMaterial.Occlusion := 1.0;
  LMaterial.Roughness := Vector3f(0.9, 0.7, 0.2);
  LMaterial.Technique := TTechniqueLighting.CookTorrance;
  LMaterial.Bitmask := $02;

  // (2) Roadblocks and traffic drums
  LMaterial := FMaterials.Add;
  LMaterial.AmbientColor := FloatColorRGB(0.2, 0.2, 0.2);
  LMaterial.AlbedoColor := FloatColor(1.0, 1.0, 1.0, 0.5);
  LMaterial.SpecularColor := TFloatColorRGB.White;
  LMaterial.SpecularExponent := 4.0;
  LMaterial.Occlusion := 1.0;
  LMaterial.Technique := TTechniqueLighting.Phong;
  LMaterial.Bitmask := $02;
end;

procedure TApplicationHelper.ReleaseObjectLightTracking;
var
  LObject: TObjectModel;
  LTracking: PObjectLightTracking;
begin
  for LObject in FObjects do
  begin
    LTracking := LObject.Payload;
    if LTracking <> nil then
    begin
      if LTracking.Movement <> nil then
        Dispose(LTracking.Movement);

      Dispose(LTracking);
    end;
  end;
end;

var
  ApplicationHelper: TApplicationHelper = nil;

begin
  ExitCode := 0;
  try
    ApplicationHelper := TApplicationHelper.Create;
    try
      if not ApplicationHelper.Execute then
        ExitCode := -2;
    finally
      ApplicationHelper.Free;
    end;
  except
    on E: Exception do
    begin
      DisplayErrorMessage(E.Message);
      ExitCode := -1;
    end;
  end;
end.