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:
//  * Real-time volumetric text rendering in 3D using "TextModeller" module, including "curved" text.
//  * Ground fog effect, which simulates fog densely packed near certain surface under certain height.
//  * Two independent shadow-casting light sources, HDR rendering and bloom.
//  * Timer-based rendering, which ensures fixed-rate rendering playback.
//  * Third-person view camera with location-based zoom (zooms in or out where the mouse is pointing at).

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;
    FTextModeller: TTextModeller;
    FTimer: TTimer;

    FObjects: TObjectModels;
    FLights: TSceneLights;
    FTextureCabinet: TTextureCabinet;
    FShadowCastingAtlas: TShadowCastingAtlas;
    FShadowCasterViews: array[0..1] of TObjectModelView;

    FSpatialFog: TSpatialFog;
    FModelFloor: TMeshModel;

    FSceneDepthNormals: TScene;
    FSceneModeling: TScene;

    FCamera: TActorCamera;
    FProjection: TMatrix4f;
    FFrameIndex: Integer;

    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 ApplicationIdle(Sender: TObject; var Done: Boolean);
    procedure RenderingControlPaint(Sender: TObject);
    procedure RenderText(const AScene: TScene);
    procedure RenderFloorModel(const AScene: TScene);
    procedure PrepareTextForRendering;
    procedure CreateRenderingControl;
  public
  end;

var
  MainForm: TMainForm;

implementation
{$R *.lfm}

uses
  LCLType, SysUtils, Math;

const
  VertexElements: array[0..1] of TVertexElement = (
    (Name: 'vertexPosition'; Format: TElementFormat.Float; Count: 3; Channel: 0; Offset: 0),
    (Name: 'vertexNormal'; Format: TElementFormat.Float; Count: 3; Channel: 0; Offset: 0));

procedure TMainForm.FormCreate(Sender: TObject);
var
  LDepthClipNegative: Boolean;
  LToneMappingBloom: TToneMappingBloom;
  LBuffer: TMeshBuffer;
  LLight: PSceneLight;
  I: Integer;
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);
    FTextModeller := TTextModeller.Create(FDevice);
    FLights := TSceneLights.Create(FDevice);
    FObjects := TObjectModels.Create(TSceneMeshes(NullObject));

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

    // Configure tone-mapping/bloom parameters.
    LToneMappingBloom := FTextureCabinet.ToneMappingBloom;
    LToneMappingBloom.ToneWhite := 2.0;
    FTextureCabinet.ToneMappingBloom := LToneMappingBloom;

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

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

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

    for I := 0 to 1 do
    begin
      LLight := FLights.Add;
      LLight.AmbientColor := TFloatColorRGB.White;
      LLight.AlbedoColor := TFloatColorRGB.White;
      LLight.SpecularColor := TFloatColorRGB.White;
      LLight.SpecularExponent := 4.0;
      LLight.Intensity := 1.0;
      LLight.ShadowCaster := I;
      LLight.Position := Vector3f(300.0, 800.0, -300.0);
      if I = 1 then
        LLight.Position := Vector3f(-300.0, 800.0, 300.0);

      FShadowCasterViews[I] := FObjects.CreateView;
      FShadowCasterViews[I].View := TMatrix4f.LookAt(LLight.Position, TVector3f.Zero, TVector3f.AxisY);
      FShadowCasterViews[I].SetProjection(TMatrix4f.PerspectiveFOVY(Pi * 0.3, 1.0, 200.0, 1400.0,
        LDepthClipNegative), LDepthClipNegative);

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

    FSpatialFog := TSpatialFog.Create(FDevice);
    FTimer := TTimer.Create;
    FTimer.Speed := 60.0;

    FCamera := TActorCamera.Create(TVector3f.Zero, Vector3f(Pi * 0.4, 0.0, 0.0), 400.0);
    FCamera.Constraints := TCameraConstraints.Create(
      Vector4f(-200.0, -50.0, -200.0, 300.0),
      Vector4f( 200.0,  50.0,  200.0, 600.0),
      Vector4f(Pi * 0.01, 0.0, 0.0, 0.0),
      Vector4f(Pi * 0.4, 0.0, 0.0, 0.0));

    LBuffer := TMeshBuffer.Create;
    try
      LBuffer.Plane(1, 1, TVector3f.Zero, Vector3f(1200.0, 0.0, 0.0), Vector3f(0.0, 0.0, 1200.0),
        TVector3f.AxisY, TPoint2f.Zero, TPoint2f.Unity, TFloatColor.White);

      FModelFloor := LBuffer.Model(FDevice, VertexElements);
    finally
      LBuffer.Free;
    end;
  except
    on E: EAfterwarpException do
    begin
      MessageDlg(E.Message, mtError, [mbOK], 0);
      Application.Terminate;
      Exit;
    end;
  end;
  Application.OnIdle := ApplicationIdle;
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  FModelFloor.Free;
  FCamera.Free;
  FTimer.Free;
  FSpatialFog.Free;
  FSceneModeling.Free;
  FSceneDepthNormals.Free;
  FShadowCastingAtlas.Free;
  FTextureCabinet.Free;
  FObjects.Free;
  FLights.Free;
  FTextModeller.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;
end;

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

  FProjection := TMatrix4f.PerspectiveFOVY(Pi * 0.25, FDisplaySize.X / FDisplaySize.Y,
    10.0, 1200.0, LDepthClipNegative);

  // Perform light assignment for 3D scene.
  FLights.ViewSize := FDisplaySize;
  FLights.ClustersCullingMode := TClustersCullingMode.Performance;
  FLights.ClusterSize := 256;
  FLights.DepthSlices := 4;
  FLights.Execute(FCamera.View, FProjection);

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

  // Pre-render text in 3D.
  PrepareTextForRendering;

  // Render shadow maps.
  FShadowCastingAtlas.BorderFill;

  for I := 0 to FShadowCastingAtlas.CasterCount - 1 do
  begin
    LShadowCaster := FShadowCastingAtlas.Casters[I];
    LShadowCaster.ViewProjection := FShadowCasterViews[I].ViewProjection;

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

      FSceneDepthNormals.ActiveVertexElements := 0;
      FSceneDepthNormals.BeginScene;
      try
        RenderFloorModel(FSceneDepthNormals);
      finally
        FSceneDepthNormals.EndScene;
      end;

      FSceneDepthNormals.ActiveVertexElements := 1;
      FSceneDepthNormals.BeginScene;
      try
        RenderText(FSceneDepthNormals);
      finally
        FSceneDepthNormals.EndScene;
      end;
    finally
      LShadowCaster.EndScene;
    end;
    LShadowCaster.Filter;
  end;

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

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

    FSceneDepthNormals.ActiveVertexElements := 0;
    FSceneDepthNormals.BeginScene;
    try
      RenderFloorModel(FSceneDepthNormals);
    finally
      FSceneDepthNormals.EndScene;
    end;

    FSceneDepthNormals.ActiveVertexElements := 1;
    FSceneDepthNormals.BeginScene;
    try
      RenderText(FSceneDepthNormals);
    finally
      FSceneDepthNormals.EndScene;
    end;
  finally
    FTextureCabinet.EndScene;
  end;
  FTextureCabinet.Filter(TTextureCabinetFilterType.Occlusion, FProjection);

  // In order to ensure rendering exactly 60 frames per second, the following function call waits until
  // next time slice is reached before returning, issuing an appropriate yield and/or sleep command as
  // necessary. Traditionally, this is called at the beginning of rendering call. However, to take
  // better advantage of parallelism with GPU, this function is called here after depth pre-pass, which
  // would start ambient occlusion calculation (a rather heavy-weight GPU task). Therefore, while the
  // current thread waits for next time slice (if such hasn't been reached yet), the GPU continues
  // working.
  FTimer.NextSlice;

  // Render main scene.
  FTextureCabinet.Clear(TTextureCabinetPass.Color, FloatColor(2.0, 2.0, 2.0, 1.0));
  FTextureCabinet.BeginScene(TTextureCabinetPass.Color);
  try
    FSceneModeling.Lights := FLights;
    FSceneModeling.ShadowCastingAtlas := FShadowCastingAtlas;
    FSceneModeling.TextureCabinet := FTextureCabinet;

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

    FSceneModeling.Prepare;
    FSceneModeling.Attributes := LAttributes;
    FSceneModeling.ActiveVertexElements := 0;
    FSceneModeling.BeginScene;
    try
      RenderFloorModel(FSceneModeling);
    finally
      FSceneModeling.EndScene;
    end;

    FSceneModeling.Attributes := LAttributes + [TSceneAttribute.Coloring];
    FSceneModeling.ActiveVertexElements := 1;
    FSceneModeling.BeginScene;
    try
      RenderText(FSceneModeling);
    finally
      FSceneModeling.EndScene;
    end;
  finally
    FTextureCabinet.EndScene;
  end;
  // Apply fog.
  LFogParameters := TFogParameters.Default;
  LFogParameters.Color := FloatColor(2.0, 2.0, 2.0, 0.0);
  LFogParameters.Opacity := 1.0;
  LFogParameters.GroundPlane := Vector4f(0.0, 1.0, 0.0, -100.0);
  LFogParameters.Scattering := 1.0;
  LFogParameters.Extinction := 0.003;
  LFogParameters.DensityGround := 0.03;
  FSpatialFog.Parameters := LFogParameters;

  FSpatialFog.Formula := TFogFormula.Ground;
  FSpatialFog.Distance := TDepthFogDistance.EyeRelative;
  FSpatialFog.View := FCamera.View;
  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.Attributes := [];
      FCanvas.ContextState := TCanvasContextState.FlatText;
      FCanvas.SamplerState := TCanvasSamplerState.Default;

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

      FTextRenderer.DrawAligned(Point2f(FDisplaySize.X * 0.5, FDisplaySize.Y - 4.0 * FDisplayScale),
        'Rotate and move the camera around to see ground-based fog.', ColorPair($FFFF4040, $FFFFFF40),
        TTextAlignment.Middle, TTextAlignment.Ending);

      LFontParams := TFontParameters.Create('Segoe UI', 14.0 * FDisplayScale, TFontWeight.SemiBold);
      LFontParams.Effect.BorderType := TFontBorder.SemiHeavy;
      LFontParams.Effect.BorderBrightness := 1.0;
      LFontParams.Effect.FillBrightness := 0.0;
      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}

  FTimer.Update;
  Inc(FFrameIndex, FTimer.Tokens);
end;

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

  if Button = mbLeft then
    FCamera.Command(TCameraCommand.Rotation, Vector3f(FMousePos.X, FMousePos.Y, 0.0), FDisplaySize)
  else
    FCamera.Command(TCameraCommand.Dragging, Vector3f(FMousePos.X, FMousePos.Y, 0.0), FDisplaySize);
end;

procedure TMainForm.RenderingControlMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
  FMousePos := Point2i(X, Y);
  FCamera.Command(TCameraCommand.Update, Vector3f(FMousePos.X, FMousePos.Y, 0.0), FDisplaySize);
end;

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

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

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

procedure TMainForm.ApplicationIdle(Sender: TObject; var Done: Boolean);
begin
  FRenderingControl.Invalidate;
  Done := False;
end;

procedure TMainForm.RenderText(const AScene: TScene);
var
  LMaterial: TObjectMaterial;
begin
  if AScene.Handle = FSceneModeling.Handle then
  begin
    LMaterial := TObjectMaterial.Default;
    LMaterial.AmbientColor := FloatColorRGB(0.25, 0.25, 0.25);
    LMaterial.AlbedoColor := FloatColor(1.0, 1.0, 1.0, 0.5);
    LMaterial.SpecularColor := TFloatColorRGB.White;
    LMaterial.SpecularExponent := 4.0;
    LMaterial.Shadows := 1.0;
    LMaterial.Occlusion := 1.0;
    LMaterial.Roughness := Vector3f(1.8, 1.2, 0.7);
    LMaterial.Technique := TTechniqueLighting.CookTorrance;
    AScene.Material := LMaterial;
  end;
  AScene.World := TMatrix4f.Identity;
  FTextModeller.Render(AScene.SceneProgram);
end;

procedure TMainForm.RenderFloorModel(const AScene: TScene);
var
  LMaterial: TObjectMaterial;
begin
  if AScene.Handle = FSceneModeling.Handle then
  begin
    LMaterial := TObjectMaterial.Default;
    LMaterial.AmbientColor := TFloatColorRGB.White;
    LMaterial.AlbedoColor := TFloatColor.TranslucentBlack;
    LMaterial.SpecularColor := TFloatColorRGB.Black;
    LMaterial.Shadows := 1.0;
    LMaterial.Occlusion := 1.0;
    AScene.Material := LMaterial;
  end;
  AScene.World := TMatrix4f.Identity;
  FModelFloor.Draw(AScene.SceneProgram);
end;

procedure TMainForm.PrepareTextForRendering;
const
  SampleText: string = '该文本可以用多种不同的语言编写。';
begin
  FTextModeller.Clear;
  FTextModeller.Transform := TMatrix4f.Identity;

  // Draw standard extruded 3D text.
  FTextModeller.SetFontParameters(TFontParameters.Create('Cambria', 24.0, TFontWeight.SemiBold));
  FTextModeller.Draw(Vector3f(0.0, 0.0, 80.0), 'What you see is a standard text extruded in 3D.', 3.0,
    ColorPair($FFFF0000, $FFFF00FF));

  // Draw a text that changes each frame (shows frame number and skipped frames).
  // In terms of performance, there is no difference if text constantly changes or remains static.
  FTextModeller.SetFontParameters(TFontParameters.Create('Calibri', 18.0, TFontWeight.SemiBold));
  FTextModeller.Draw(Vector3f(0.0, 0.0, 40.0), Format(
    'In real-time, frame index is (%d), skipped frames (%d).', [FFrameIndex, FTimer.SkippedTimeSlices]),
    3.0, ColorPair($FFFF0000, $FFFF7070));

  // Draw some more complicated 3D text, in Chinese (Simplified).
  FTextModeller.SetFontParameters(TFontParameters.Create('Segoe UI', 24.0, TFontWeight.Bold));
  FTextModeller.Draw(Vector3f(0.0, 0.0, -40.0), Utf8String(SampleText), 6.0,
    ColorPair($FF008080, $FF00FF80));

  // The following uses "italic" font slant.
  FTextModeller.SetFontParameters(TFontParameters.Create('Calibri', 14.0, TFontWeight.Normal,
    TFontStretch.Normal, TFontSlant.Italic));
  FTextModeller.Draw(Vector3f(0.0, 0.025, 110.0), 'Text in 3D can also be flat (no extrusion).', 0.0,
    ColorPair($FF000000));

  // Draw curved text rotated inside its own plane.
  FTextModeller.SetFontParameters(TFontParameters.Create('Segoe UI', 24.0, TFontWeight.Bold));
  FTextModeller.DrawCurved(Vector3f(0.0, 60.0, 0.0), 'This text is rendered at certain angle.', -200.0, NAN,
    5.0, ColorPair($FF0000FF, $FF00E0FF));

  // Draw curved text rotated around "depth" axis.
  FTextModeller.DrawDepthCurved(Vector3f(0.0, 80.0, 0.0), 'Curve rendering test application.', -200.0, NAN,
    5.0, ColorPair($FF00FF00, $FFFFFFFF));

  FTextMOdeller.Prepare;
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.
