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.