Synthwave

For the past few weeks I've been working on writing and testing an object oriented encapsulation of the free open source SDL and NanoVG libraries.
Below is a short video clip of one of my demos, the Synthwave demo. This demo evokes the style of the 1980s synthwave music culture using Free Pascal. The user interface elements are also rendered using vector graphics.

Video

Here is a video capture of the Synthwave vector graphics demo.
Video: Synthwave


Source Code

The following is a complete listing of the Sythwave demo.
unit Demo.Synthwave;

{$mode delphi}

interface

uses
  Tiny.System, Tiny.Types, Tiny.Geometry, Tiny.Application, Tiny.Graphics,
  Demo.Scene;

{ TSynthwave }

type
  TPoints = TArrayList<TVec3>;
  TPointList = TArrayList<TPoints>;

  TSynthwave = class(TDemoScene)
  private
    FPoints: TPointList;
    FLast: Double;
    FSpeed: Float;
  protected
    function GetTitle: string; override;
    function GetDescription: string; override;
    function GetGlyph: string; override;
    function RangeGenerate: TRangeInfo; override;
    procedure RangeChanged(NewPosition: Float); override;
    procedure Load; override;
    procedure Render(Width, Height: Integer; const Time: Double); override;
  end;

implementation

const
  ListLength = 30;
  PointsLength = 60;
  Spacing = 40;
  Offset = PointsLength * Spacing / 2;
  Depth = 300;

function TSynthwave.GetTitle: string;
begin
  Result := 'Synthwave';
end;

function TSynthwave.GetDescription: string;
begin
  Result := 'Synthwave stems from a microgenre of electronic music drawing predominantly from 1980s culture.';
end;

function TSynthwave.GetGlyph: string;
begin
  Result := '󱑽';
end;

function TSynthwave.RangeGenerate: TRangeInfo;
begin
  Result := TRangeInfo.Create('Driving speed', FSpeed, 0.001, 0, 10);
end;

procedure TSynthwave.RangeChanged(NewPosition: Float);
begin
  FSpeed := NewPosition;
end;

procedure TSynthwave.Load;
var
  PointData: TPoints;
  A, I, J: Integer;
begin
  inherited Load;
  { Reset the speed timer }
  FLast := 0;
  if FPoints.Length = 0 then
    FSpeed := 1;
  FPoints.Length := ListLength;
  { Generate our ground geometry }
  for I := 0 to ListLength - 1 do
  begin
    PointData := nil;
    PointData.Length := PointsLength;
    for J := 0 to PointsLength - 1 do
    begin
      A := Abs(J - ListLength);
      PointData[J] := Vec3(J * Spacing, Random * -(A * A) + ListLength, I * -10);
    end;
    FPoints[I] := PointData;
  end;
end;

procedure TSynthwave.Render(Width, Height: Integer; const Time: Double);
const
{ Skip allows for reduction in overall scene geometry }
  Skip = 6;
  colorDusk = $FF6E073A;
  colorCorona = $00FF0000;
var
  Brush: IGradientBrush;
  Rect: TRectF;
  Delta: Double;
  Reset: Boolean;
  A, B: Float;
  I, J: Integer;
begin
  inherited Render(Width, Height, Time);
  { Draw background }
  Brush := NewBrush(NewPointF(0, Height * 0.6), NewPointF(0, 0));
  Brush.NearStop.Color := colorDusk;
  Brush.FarStop.Color := colorBlack;
  Canvas.Rect(ClientRect);
  Canvas.Fill(Brush);
  { Draw corona }
  Rect := NewRectF(350, 350);
  Rect.X := Width / 2 - Rect.Width / 2;
  Rect.Y := Height / 2 - Rect.Height / 2;
  Brush := NewBrush(Rect);
  Brush.FarStop.Offset := 0.9;
  Brush.NearStop.Color := colorOrange;
  Brush.NearStop.Offset := 0.1;
  Brush.FarStop.Color := colorCorona;
  Canvas.Circle(Width / 2, Height / 2, 350);
  Canvas.Fill(Brush);
  { Draw sun }
  Brush := NewBrush(NewPointF(0, Height * 0.7), NewPointF(0, Height * 0.3));
  Brush.NearStop.Color := colorRed;
  Brush.FarStop.Color := colorYellow;
  Canvas.Circle(Width / 2, Height / 2, 225);
  Canvas.Fill(Brush);
  { Adjust for speed }
  Delta := (Time - FLast) * 50 * FSpeed;
  FLast := Time;
  { Check if we need to rebuild next row }
  for I := 0 to ListLength - 1 do
  begin
    Reset := False;
    for J := Skip to PointsLength - (Skip + 1) do
    begin
      FPoints[I].Items[J].Z := FPoints[I].Items[J].Z - 0.5 * Delta;
      if FPoints[I].Items[J].Z < -Depth then
      begin
        Reset := True;
        Break;
      end;
    end;
    { Rebuild row }
    if Reset then
    begin
      FPoints.Move(ListLength - 1, 0);
      for J := Skip to PointsLength - (Skip + 1) do
      begin
        A := Abs(J - ListLength);
        FPoints[0].Items[J].Z := 0;
        FPoints[0].Items[J].Y := Random * -(A * A) + ListLength;
      end;
    end;
  end;
  { Draw the ground grid }
  Canvas.Matrix.Translate(Width / 2, Height / 2);
  for I := 0 to ListLength - 2 do
    for J := Skip to PointsLength - (Skip + 2) do
    begin
      A := Depth / (Depth + FPoints[I][J].Z);
      B := Depth / (Depth + FPoints[I + 1][J].Z);
      Canvas.MoveTo((FPoints[I][J].X - Offset) * A, FPoints[I][J].Y * A);
      Canvas.LineTo((FPoints[I][J + 1].X - Offset) * A, FPoints[I][J + 1].Y * A);
      Canvas.LineTo((FPoints[I + 1][J + 1].X - Offset) * B, FPoints[I + 1][J + 1].Y * B);
      Canvas.LineTo((FPoints[I + 1][J].X - Offset) * B, FPoints[I + 1][J].Y * B);
      { Fill and stroke }
      Canvas.Fill(NewColorF(0, 0, 0, -FPoints[I][J].Z / 100), True);
      A := Depth + FPoints[I][J].Z;
      Canvas.Stroke(NewColorF((250 - A) / 255, 0, (50 + A) / 255, 1 - A / 300), 2);
    end;
end;

end.