.
This commit is contained in:
@@ -5,20 +5,30 @@
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>DoomDeathmatchConsole</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DoomDeathmatch\DoomDeathmatch.csproj" />
|
||||
<ProjectReference Include="..\Engine\Engine.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Sylvan.BuildTools.Resources" Version="0.6.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<Compile Update="src\Resource\ShaderResource.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>ShaderResource.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="src\Resource\ShaderResource.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>ShaderResource.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="src\Resource\ShaderResource.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>ShaderResource.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup><StaticResourceFolder Include="assets"/></ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
84
PresenterConsole/assets/shader/ascii.shader
Normal file
84
PresenterConsole/assets/shader/ascii.shader
Normal file
@@ -0,0 +1,84 @@
|
||||
// #type vertex
|
||||
#version 460 core
|
||||
|
||||
layout (location = 0) in vec2 aPos;
|
||||
layout (location = 1) in vec2 aUV;
|
||||
|
||||
layout (location = 0) out vec2 oUV;
|
||||
|
||||
void main()
|
||||
{
|
||||
oUV = aUV;
|
||||
gl_Position = vec4(aPos, 0.0, 1.0);
|
||||
}
|
||||
|
||||
// #type fragment
|
||||
#version 460 core
|
||||
|
||||
precision highp float;
|
||||
|
||||
// All components are in the range [0…1], including hue.
|
||||
vec3 rgb2hsv(vec3 c)
|
||||
{
|
||||
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
|
||||
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
|
||||
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
|
||||
|
||||
float d = q.x - min(q.w, q.y);
|
||||
float e = 1.0e-10;
|
||||
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
|
||||
}
|
||||
|
||||
// 16 Windows color palette (approximated)
|
||||
const vec3 windowsPalette[7] = vec3[](
|
||||
vec3(rgb2hsv(vec3(0.0, 0.0, 1.0)).rg, 0.0), // Blue (FOREGROUND_BLUE)
|
||||
vec3(rgb2hsv(vec3(0.0, 1.0, 0.0)).rg, 0.0), // Green (FOREGROUND_GREEN)
|
||||
vec3(rgb2hsv(vec3(0.0, 1.0, 1.0)).rg, 0.0), // Cyan (FOREGROUND_GREEN | FOREGROUND_BLUE)
|
||||
vec3(rgb2hsv(vec3(1.0, 0.0, 0.0)).rg, 0.0), // Red (FOREGROUND_RED)
|
||||
vec3(rgb2hsv(vec3(1.0, 0.0, 1.0)).rg, 0.0), // Magenta (FOREGROUND_RED | FOREGROUND_BLUE)
|
||||
vec3(rgb2hsv(vec3(1.0, 1.0, 0.0)).rg, 0.0), // Yellow (FOREGROUND_RED | FOREGROUND_GREEN)
|
||||
vec3(rgb2hsv(vec3(1.0, 1.0, 1.0)).rg, 0.0) // White (all colors set)
|
||||
);
|
||||
|
||||
// Find the closest color in the Windows palette
|
||||
int findClosestColor(vec3 color) {
|
||||
int closestIndex = 0;
|
||||
float minDistance = distance(color, windowsPalette[0]);
|
||||
|
||||
for (int i = 1; i < 7; i++) {
|
||||
float dist = distance(color, windowsPalette[i]);
|
||||
if (dist < minDistance) {
|
||||
minDistance = dist;
|
||||
closestIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
return closestIndex + 1;
|
||||
}
|
||||
|
||||
float calculateLuminosity(vec3 color) {
|
||||
// Standard luminosity calculation
|
||||
float luminosity = dot(color, vec3(0.299, 0.587, 0.114));
|
||||
return clamp(luminosity, 0.0, 1.0);
|
||||
}
|
||||
|
||||
uniform sampler2D uInputTexture;
|
||||
|
||||
layout (location = 0) in vec2 iUV;
|
||||
layout (location = 0) out vec4 FragColor;
|
||||
|
||||
void main() {
|
||||
vec3 pixelColor = texture(uInputTexture, iUV).rgb;
|
||||
vec3 pixelColorHsv = rgb2hsv(pixelColor);
|
||||
|
||||
// Find closest color index (4 bits)
|
||||
int colorIndex = findClosestColor(vec3(pixelColorHsv.rg, 0));
|
||||
|
||||
// Calculate luminosity (4 bits)
|
||||
float luminosity = calculateLuminosity(pixelColor);
|
||||
|
||||
// Combine into a single byte-like value
|
||||
// High 4 bits: color index
|
||||
// Low 4 bits: luminosity
|
||||
FragColor = vec4(luminosity, colorIndex / 255.0, 0, 1);
|
||||
}
|
||||
21
PresenterConsole/src/AsciiPixel.cs
Normal file
21
PresenterConsole/src/AsciiPixel.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Engine.Graphics.Pixel;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
|
||||
namespace PresenterConsole;
|
||||
|
||||
public struct AsciiPixel : IPixel
|
||||
{
|
||||
public PixelFormat Format => PixelFormat.Rg;
|
||||
public PixelType Type => PixelType.UnsignedByte;
|
||||
|
||||
public PixelInternalFormat InternalFormat => PixelInternalFormat.Rg8;
|
||||
public SizedInternalFormat SizedInternalFormat => SizedInternalFormat.Rg8;
|
||||
|
||||
public byte LightnessIndex => R;
|
||||
public byte ColorIndex => G;
|
||||
|
||||
public byte R;
|
||||
public byte G;
|
||||
|
||||
public override string ToString() => $"{R}, {G}";
|
||||
}
|
||||
11
PresenterConsole/src/AsciiVertex.cs
Normal file
11
PresenterConsole/src/AsciiVertex.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Engine.Graphics.Buffer.Vertex;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using OpenTK.Mathematics;
|
||||
|
||||
namespace PresenterConsole;
|
||||
|
||||
public struct AsciiVertex : IVertex
|
||||
{
|
||||
[Vertex(VertexAttribType.Float, 2)] public Vector2 _position;
|
||||
[Vertex(VertexAttribType.Float, 2)] public Vector2 _uv;
|
||||
}
|
||||
158
PresenterConsole/src/ConsoleFastOutput.cs
Normal file
158
PresenterConsole/src/ConsoleFastOutput.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace PresenterConsole;
|
||||
|
||||
public class ConsoleFastOutput
|
||||
{
|
||||
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
static extern SafeFileHandle CreateFile(
|
||||
string fileName,
|
||||
[MarshalAs(UnmanagedType.U4)] uint fileAccess,
|
||||
[MarshalAs(UnmanagedType.U4)] uint fileShare,
|
||||
IntPtr securityAttributes,
|
||||
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
|
||||
[MarshalAs(UnmanagedType.U4)] int flags,
|
||||
IntPtr template);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
static extern bool WriteConsoleOutput(
|
||||
SafeFileHandle hConsoleOutput,
|
||||
CharInfo[] lpBuffer,
|
||||
Coord dwBufferSize,
|
||||
Coord dwBufferCoord,
|
||||
ref SmallRect lpWriteRegion);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Coord
|
||||
{
|
||||
public short X;
|
||||
public short Y;
|
||||
|
||||
public Coord(short X, short Y)
|
||||
{
|
||||
this.X = X;
|
||||
this.Y = Y;
|
||||
}
|
||||
};
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct CharUnion
|
||||
{
|
||||
[FieldOffset(0)] public char UnicodeChar;
|
||||
[FieldOffset(0)] public byte AsciiChar;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct CharInfo
|
||||
{
|
||||
[FieldOffset(0)] public CharUnion Char;
|
||||
[FieldOffset(2)] public short Attributes;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct SmallRect
|
||||
{
|
||||
public short Left;
|
||||
public short Top;
|
||||
public short Right;
|
||||
public short Bottom;
|
||||
}
|
||||
|
||||
private readonly SafeFileHandle _handle;
|
||||
private CharInfo[] _buffer;
|
||||
private SmallRect _region;
|
||||
private int _width;
|
||||
private int _height;
|
||||
|
||||
public ConsoleFastOutput(int parWidth, int parHeight)
|
||||
{
|
||||
_width = parWidth;
|
||||
_height = parHeight;
|
||||
_handle = CreateFile("CONOUT$", 0x40000000, 2, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
|
||||
if (_handle.IsInvalid)
|
||||
{
|
||||
throw new Exception("Failed to open console");
|
||||
}
|
||||
|
||||
_buffer = new CharInfo[parWidth * parHeight];
|
||||
_region = new SmallRect { Left = 0, Top = 0, Right = (short)_width, Bottom = (short)_height };
|
||||
}
|
||||
|
||||
public void WriteChar(char parChar, short parX, short parY, ushort parForeground, ushort parBackground)
|
||||
{
|
||||
var index = parX + parY * _width;
|
||||
if (index >= _buffer.Length)
|
||||
return;
|
||||
|
||||
_buffer[parX + parY * _width].Char.UnicodeChar = parChar;
|
||||
_buffer[parX + parY * _width].Attributes = (short)(parForeground | (parBackground << 4));
|
||||
}
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
var regions = SplitRegion(_region, 1, 1);
|
||||
for (var i = 0; i < regions.Length; i++)
|
||||
{
|
||||
var region = regions[i];
|
||||
var bufferSize = new Coord(region.Right, region.Bottom);
|
||||
var bufferCoord = new Coord(region.Left, region.Top);
|
||||
WriteConsoleOutput(_handle, _buffer, bufferSize, bufferCoord, ref region);
|
||||
}
|
||||
|
||||
// var bufferSize = new Coord(_region.Right, _region.Bottom);
|
||||
// var bufferCoord = new Coord(0, 0);
|
||||
// WriteConsoleOutput(_handle, _buffer, bufferSize, bufferCoord, ref _region);
|
||||
//
|
||||
// _region.Left = 0;
|
||||
// _region.Top = 0;
|
||||
// _region.Right = (short)_width;
|
||||
// _region.Bottom = (short)_height;
|
||||
|
||||
// run in parallel
|
||||
// Parallel.ForEach(regions, region =>
|
||||
// {
|
||||
// var bufferSize = new Coord(_region.Right, _region.Bottom);
|
||||
// var bufferCoord = new Coord(region.Left, region.Top);
|
||||
// WriteConsoleOutput(_handle, _buffer, bufferSize, bufferCoord, ref region);
|
||||
// });
|
||||
}
|
||||
|
||||
private SmallRect[] SplitRegion(SmallRect parRegion, int countX, int countY)
|
||||
{
|
||||
var regions = new SmallRect[countX * countY];
|
||||
for (var y = 0; y < countY; y++)
|
||||
{
|
||||
for (var x = 0; x < countX; x++)
|
||||
{
|
||||
var region = parRegion;
|
||||
region.Left = (short)(parRegion.Left + x * parRegion.Right / countX);
|
||||
region.Right = (short)(parRegion.Left + (x + 1) * parRegion.Right / countX);
|
||||
region.Top = (short)(parRegion.Top + y * parRegion.Bottom / countY);
|
||||
region.Bottom = (short)(parRegion.Top + (y + 1) * parRegion.Bottom / countY);
|
||||
regions[y * countX + x] = region;
|
||||
}
|
||||
}
|
||||
|
||||
return regions;
|
||||
}
|
||||
|
||||
public void Resize(int parWidth, int parHeight)
|
||||
{
|
||||
if (parWidth <= 0 || parHeight <= 0)
|
||||
{
|
||||
throw new ArgumentException("Width and height must be greater than 0");
|
||||
}
|
||||
|
||||
if (parWidth == _width && parHeight == _height)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_width = parWidth;
|
||||
_height = parHeight;
|
||||
|
||||
_buffer = new CharInfo[parWidth * parHeight];
|
||||
_region = new SmallRect { Left = 0, Top = 0, Right = (short)parWidth, Bottom = (short)parHeight };
|
||||
}
|
||||
}
|
||||
@@ -4,126 +4,126 @@ namespace PresenterConsole;
|
||||
|
||||
public class ConsoleInputHandler : IInputHandler
|
||||
{
|
||||
private readonly HashSet<ConsoleKey> _currentKeys = [];
|
||||
private readonly HashSet<ConsoleKey> _previousKeys = [];
|
||||
private ConsoleModifiers _currentModifiers;
|
||||
private ConsoleModifiers _previousModifiers;
|
||||
private readonly HashSet<ConsoleKey> _currentKeys = [];
|
||||
private readonly HashSet<ConsoleKey> _previousKeys = [];
|
||||
private ConsoleModifiers _currentModifiers;
|
||||
private ConsoleModifiers _previousModifiers;
|
||||
|
||||
public void Update(double deltaTime)
|
||||
public void Update(double parDeltaTime)
|
||||
{
|
||||
// Save previous state
|
||||
_previousKeys.Clear();
|
||||
foreach (var key in _currentKeys)
|
||||
{
|
||||
// Save previous state
|
||||
_previousKeys.Clear();
|
||||
foreach (var key in _currentKeys)
|
||||
{
|
||||
_previousKeys.Add(key);
|
||||
}
|
||||
|
||||
_previousModifiers = _currentModifiers;
|
||||
|
||||
// Clear current state
|
||||
_currentKeys.Clear();
|
||||
_currentModifiers = 0;
|
||||
|
||||
// Read keys
|
||||
while (Console.KeyAvailable)
|
||||
{
|
||||
var keyInfo = Console.ReadKey(intercept: true);
|
||||
_currentKeys.Add(keyInfo.Key);
|
||||
_currentModifiers |= keyInfo.Modifiers;
|
||||
}
|
||||
_previousKeys.Add(key);
|
||||
}
|
||||
|
||||
public bool IsKeyPressed(KeyCode keyCode)
|
||||
{
|
||||
if (IsModifierKey(keyCode))
|
||||
return IsModifierActive(keyCode);
|
||||
_previousModifiers = _currentModifiers;
|
||||
|
||||
return _currentKeys.Contains(ConvertToConsoleKey(keyCode));
|
||||
// Clear current state
|
||||
_currentKeys.Clear();
|
||||
_currentModifiers = 0;
|
||||
|
||||
// Read keys
|
||||
while (Console.KeyAvailable)
|
||||
{
|
||||
var keyInfo = Console.ReadKey(intercept: true);
|
||||
_currentKeys.Add(keyInfo.Key);
|
||||
_currentModifiers |= keyInfo.Modifiers;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsKeyJustPressed(KeyCode keyCode)
|
||||
{
|
||||
if (IsModifierKey(keyCode))
|
||||
return IsModifierActive(keyCode) && !WasModifierActive(keyCode);
|
||||
public bool IsKeyPressed(KeyCode keyCode)
|
||||
{
|
||||
if (IsModifierKey(keyCode))
|
||||
return IsModifierActive(keyCode);
|
||||
|
||||
var consoleKey = ConvertToConsoleKey(keyCode);
|
||||
return _currentKeys.Contains(consoleKey) && !_previousKeys.Contains(consoleKey);
|
||||
}
|
||||
return _currentKeys.Contains(ConvertToConsoleKey(keyCode));
|
||||
}
|
||||
|
||||
public bool IsKeyRepeat(KeyCode keyCode)
|
||||
{
|
||||
if (IsModifierKey(keyCode))
|
||||
return IsModifierActive(keyCode) && WasModifierActive(keyCode);
|
||||
public bool IsKeyJustPressed(KeyCode keyCode)
|
||||
{
|
||||
if (IsModifierKey(keyCode))
|
||||
return IsModifierActive(keyCode) && !WasModifierActive(keyCode);
|
||||
|
||||
var consoleKey = ConvertToConsoleKey(keyCode);
|
||||
return _currentKeys.Contains(consoleKey) && _previousKeys.Contains(consoleKey);
|
||||
}
|
||||
var consoleKey = ConvertToConsoleKey(keyCode);
|
||||
return _currentKeys.Contains(consoleKey) && !_previousKeys.Contains(consoleKey);
|
||||
}
|
||||
|
||||
public bool IsMouseButtonPressed(MouseButton button)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
public bool IsKeyRepeat(KeyCode keyCode)
|
||||
{
|
||||
if (IsModifierKey(keyCode))
|
||||
return IsModifierActive(keyCode) && WasModifierActive(keyCode);
|
||||
|
||||
public bool IsMouseButtonJustPressed(MouseButton button)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var consoleKey = ConvertToConsoleKey(keyCode);
|
||||
return _currentKeys.Contains(consoleKey) && _previousKeys.Contains(consoleKey);
|
||||
}
|
||||
|
||||
private static bool IsModifierKey(KeyCode keyCode) => keyCode is KeyCode.Ctrl or KeyCode.Backspace or KeyCode.Shift;
|
||||
public bool IsMouseButtonPressed(MouseButton button)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsModifierActive(KeyCode keyCode) => keyCode switch
|
||||
{
|
||||
KeyCode.Ctrl => (_currentModifiers & ConsoleModifiers.Control) != 0,
|
||||
KeyCode.Alt => (_currentModifiers & ConsoleModifiers.Alt) != 0,
|
||||
KeyCode.Shift => (_currentModifiers & ConsoleModifiers.Shift) != 0,
|
||||
_ => false
|
||||
};
|
||||
public bool IsMouseButtonJustPressed(MouseButton button)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool WasModifierActive(KeyCode keyCode) => keyCode switch
|
||||
{
|
||||
KeyCode.Ctrl => (_previousModifiers & ConsoleModifiers.Control) != 0,
|
||||
KeyCode.Alt => (_previousModifiers & ConsoleModifiers.Alt) != 0,
|
||||
KeyCode.Shift => (_previousModifiers & ConsoleModifiers.Shift) != 0,
|
||||
_ => false
|
||||
};
|
||||
private static bool IsModifierKey(KeyCode keyCode) => keyCode is KeyCode.Ctrl or KeyCode.Backspace or KeyCode.Shift;
|
||||
|
||||
private static ConsoleKey ConvertToConsoleKey(KeyCode keyCode) => keyCode switch
|
||||
{
|
||||
KeyCode.A => ConsoleKey.A,
|
||||
KeyCode.B => ConsoleKey.B,
|
||||
KeyCode.C => ConsoleKey.C,
|
||||
KeyCode.D => ConsoleKey.D,
|
||||
KeyCode.E => ConsoleKey.E,
|
||||
KeyCode.F => ConsoleKey.F,
|
||||
KeyCode.G => ConsoleKey.G,
|
||||
KeyCode.H => ConsoleKey.H,
|
||||
KeyCode.I => ConsoleKey.I,
|
||||
KeyCode.J => ConsoleKey.J,
|
||||
KeyCode.K => ConsoleKey.K,
|
||||
KeyCode.L => ConsoleKey.L,
|
||||
KeyCode.M => ConsoleKey.M,
|
||||
KeyCode.N => ConsoleKey.N,
|
||||
KeyCode.O => ConsoleKey.O,
|
||||
KeyCode.P => ConsoleKey.P,
|
||||
KeyCode.Q => ConsoleKey.Q,
|
||||
KeyCode.R => ConsoleKey.R,
|
||||
KeyCode.S => ConsoleKey.S,
|
||||
KeyCode.T => ConsoleKey.T,
|
||||
KeyCode.U => ConsoleKey.U,
|
||||
KeyCode.V => ConsoleKey.V,
|
||||
KeyCode.W => ConsoleKey.W,
|
||||
KeyCode.X => ConsoleKey.X,
|
||||
KeyCode.Y => ConsoleKey.Y,
|
||||
KeyCode.Z => ConsoleKey.Z,
|
||||
KeyCode.Enter => ConsoleKey.Enter,
|
||||
KeyCode.Escape => ConsoleKey.Escape,
|
||||
KeyCode.Space => ConsoleKey.Spacebar,
|
||||
KeyCode.Tab => ConsoleKey.Tab,
|
||||
KeyCode.Backspace => ConsoleKey.Backspace,
|
||||
KeyCode.Up => ConsoleKey.UpArrow,
|
||||
KeyCode.Down => ConsoleKey.DownArrow,
|
||||
KeyCode.Left => ConsoleKey.LeftArrow,
|
||||
KeyCode.Right => ConsoleKey.RightArrow,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(keyCode), $"No mapping defined for {keyCode}")
|
||||
};
|
||||
private bool IsModifierActive(KeyCode keyCode) => keyCode switch
|
||||
{
|
||||
KeyCode.Ctrl => (_currentModifiers & ConsoleModifiers.Control) != 0,
|
||||
KeyCode.Alt => (_currentModifiers & ConsoleModifiers.Alt) != 0,
|
||||
KeyCode.Shift => (_currentModifiers & ConsoleModifiers.Shift) != 0,
|
||||
_ => false
|
||||
};
|
||||
|
||||
private bool WasModifierActive(KeyCode keyCode) => keyCode switch
|
||||
{
|
||||
KeyCode.Ctrl => (_previousModifiers & ConsoleModifiers.Control) != 0,
|
||||
KeyCode.Alt => (_previousModifiers & ConsoleModifiers.Alt) != 0,
|
||||
KeyCode.Shift => (_previousModifiers & ConsoleModifiers.Shift) != 0,
|
||||
_ => false
|
||||
};
|
||||
|
||||
private static ConsoleKey ConvertToConsoleKey(KeyCode keyCode) => keyCode switch
|
||||
{
|
||||
KeyCode.A => ConsoleKey.A,
|
||||
KeyCode.B => ConsoleKey.B,
|
||||
KeyCode.C => ConsoleKey.C,
|
||||
KeyCode.D => ConsoleKey.D,
|
||||
KeyCode.E => ConsoleKey.E,
|
||||
KeyCode.F => ConsoleKey.F,
|
||||
KeyCode.G => ConsoleKey.G,
|
||||
KeyCode.H => ConsoleKey.H,
|
||||
KeyCode.I => ConsoleKey.I,
|
||||
KeyCode.J => ConsoleKey.J,
|
||||
KeyCode.K => ConsoleKey.K,
|
||||
KeyCode.L => ConsoleKey.L,
|
||||
KeyCode.M => ConsoleKey.M,
|
||||
KeyCode.N => ConsoleKey.N,
|
||||
KeyCode.O => ConsoleKey.O,
|
||||
KeyCode.P => ConsoleKey.P,
|
||||
KeyCode.Q => ConsoleKey.Q,
|
||||
KeyCode.R => ConsoleKey.R,
|
||||
KeyCode.S => ConsoleKey.S,
|
||||
KeyCode.T => ConsoleKey.T,
|
||||
KeyCode.U => ConsoleKey.U,
|
||||
KeyCode.V => ConsoleKey.V,
|
||||
KeyCode.W => ConsoleKey.W,
|
||||
KeyCode.X => ConsoleKey.X,
|
||||
KeyCode.Y => ConsoleKey.Y,
|
||||
KeyCode.Z => ConsoleKey.Z,
|
||||
KeyCode.Enter => ConsoleKey.Enter,
|
||||
KeyCode.Escape => ConsoleKey.Escape,
|
||||
KeyCode.Space => ConsoleKey.Spacebar,
|
||||
KeyCode.Tab => ConsoleKey.Tab,
|
||||
KeyCode.Backspace => ConsoleKey.Backspace,
|
||||
KeyCode.Up => ConsoleKey.UpArrow,
|
||||
KeyCode.Down => ConsoleKey.DownArrow,
|
||||
KeyCode.Left => ConsoleKey.LeftArrow,
|
||||
KeyCode.Right => ConsoleKey.RightArrow,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(keyCode), $"No mapping defined for {keyCode}")
|
||||
};
|
||||
}
|
||||
@@ -1,51 +1,117 @@
|
||||
using Engine.Asset;
|
||||
using Engine.Renderer;
|
||||
using Engine.Renderer.Framebuffer;
|
||||
using Engine.Renderer.Texture;
|
||||
using Engine.Graphics;
|
||||
using Engine.Graphics.Buffer;
|
||||
using Engine.Graphics.Framebuffer;
|
||||
using Engine.Graphics.Pixel;
|
||||
using Engine.Graphics.Shader;
|
||||
using Engine.Graphics.Texture;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using OpenTK.Mathematics;
|
||||
using OpenTK.Windowing.Common;
|
||||
|
||||
namespace PresenterConsole;
|
||||
|
||||
public class ConsolePresenter : IPresenter
|
||||
{
|
||||
public bool IsExiting => false;
|
||||
public int Width => Console.WindowWidth;
|
||||
public int Height => Console.WindowHeight;
|
||||
public event Action<ResizeEventArgs>? Resize;
|
||||
public bool IsExiting => false;
|
||||
public int Width => Console.WindowWidth;
|
||||
public int Height => Console.WindowHeight;
|
||||
public event Action<ResizeEventArgs>? Resize;
|
||||
|
||||
private readonly Framebuffer _framebuffer;
|
||||
private Image<AsciiPixel> _asciiImage;
|
||||
private int _cachedWidth;
|
||||
private int _cachedHeight;
|
||||
|
||||
public ConsolePresenter(int width, int height)
|
||||
private readonly Engine.Engine _engine;
|
||||
private readonly Framebuffer _framebuffer;
|
||||
private readonly Engine.Graphics.Shader.Program _asciiProgram;
|
||||
private Image<AsciiPixel>? _asciiImage;
|
||||
|
||||
private readonly IndexBuffer _indexBuffer;
|
||||
private readonly VertexBuffer<AsciiVertex> _vertexBuffer;
|
||||
private readonly VertexArray _vertexArray;
|
||||
|
||||
private readonly ConsoleFastOutput _consoleOutput;
|
||||
|
||||
public ConsolePresenter(Engine.Engine parEngine)
|
||||
{
|
||||
_engine = parEngine;
|
||||
_asciiProgram = ProgramLoader.LoadFromSource(Resource.ShaderResource.Ascii);
|
||||
|
||||
_framebuffer = Framebuffer.Builder(Width, Height)
|
||||
.AddColorAttachment<AsciiPixel>()
|
||||
.Build();
|
||||
|
||||
_indexBuffer = new IndexBuffer([0, 1, 2, 2, 3, 0], BufferStorageFlags.MapReadBit);
|
||||
_vertexBuffer = new VertexBuffer<AsciiVertex>([
|
||||
new AsciiVertex { _position = new Vector2(-1.0f, -1.0f), _uv = new Vector2(0.0f, 0.0f) },
|
||||
new AsciiVertex { _position = new Vector2(1.0f, -1.0f), _uv = new Vector2(1.0f, 0.0f) },
|
||||
new AsciiVertex { _position = new Vector2(1.0f, 1.0f), _uv = new Vector2(1.0f, 1.0f) },
|
||||
new AsciiVertex { _position = new Vector2(-1.0f, 1.0f), _uv = new Vector2(0.0f, 1.0f) }
|
||||
], BufferStorageFlags.MapReadBit);
|
||||
_vertexArray = new VertexArray();
|
||||
_vertexArray.BindIndexBuffer(_indexBuffer);
|
||||
_vertexArray.BindVertexBuffer(_vertexBuffer);
|
||||
|
||||
_consoleOutput = new ConsoleFastOutput(Width, Height);
|
||||
}
|
||||
|
||||
public void Present(IConstTexture parTexture)
|
||||
{
|
||||
var openglTexture = (Texture)parTexture;
|
||||
|
||||
_framebuffer.Bind();
|
||||
|
||||
GL.ClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
||||
|
||||
openglTexture.BindUnit();
|
||||
|
||||
_asciiProgram.Bind();
|
||||
_asciiProgram.SetUniform("uInputTexture", 0);
|
||||
|
||||
_vertexArray.Bind();
|
||||
GL.DrawElements(PrimitiveType.Triangles, _indexBuffer.Count, DrawElementsType.UnsignedInt, 0);
|
||||
|
||||
_framebuffer.Unbind();
|
||||
|
||||
var asciiTexture = _framebuffer.TextureInternal;
|
||||
if (asciiTexture == null)
|
||||
throw new InvalidOperationException("Framebuffer texture is null");
|
||||
|
||||
if (_asciiImage == null || asciiTexture.Width != _asciiImage.Width || asciiTexture.Height != _asciiImage.Height)
|
||||
_asciiImage = new Image<AsciiPixel>(asciiTexture.Width, asciiTexture.Height);
|
||||
|
||||
asciiTexture.ReadPixels(_asciiImage);
|
||||
Output(_asciiImage);
|
||||
}
|
||||
|
||||
private void Output(Image<AsciiPixel> parImage)
|
||||
{
|
||||
var lightmap = " .-:=+*#%@".Reverse().ToArray();
|
||||
for (var y = 0; y < parImage.Height; y++)
|
||||
{
|
||||
_framebuffer = Framebuffer.Builder(width, height)
|
||||
.AddColorAttachment<AsciiPixel>()
|
||||
.Build();
|
||||
for (var x = 0; x < parImage.Width; x++)
|
||||
{
|
||||
var pixel = parImage[y, x];
|
||||
var lightnessIndex = (int)(pixel.LightnessIndex / 255.0f * (lightmap.Length - 1));
|
||||
_consoleOutput.WriteChar(lightmap[lightnessIndex], (short)x, (short)y, 0,
|
||||
pixel.ColorIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public void Present(IConstTexture texture)
|
||||
_consoleOutput.Flush();
|
||||
}
|
||||
|
||||
public void Update(double parDeltaTime)
|
||||
{
|
||||
if (_cachedWidth != Width || _cachedHeight != Height)
|
||||
{
|
||||
var openglTexture = (Texture)texture;
|
||||
_cachedWidth = Width;
|
||||
_cachedHeight = Height;
|
||||
|
||||
_framebuffer.Bind();
|
||||
openglTexture.BindUnit();
|
||||
|
||||
// TODO: render with ascii shader
|
||||
|
||||
_framebuffer.Unbind();
|
||||
|
||||
var asciiTexture = _framebuffer.TextureInternal;
|
||||
if (asciiTexture == null)
|
||||
throw new InvalidOperationException("Framebuffer texture is null");
|
||||
|
||||
if (asciiTexture.Width != _asciiImage.Width || asciiTexture.Height != _asciiImage.Height)
|
||||
_asciiImage = new Image<AsciiPixel>(asciiTexture.Width, asciiTexture.Height);
|
||||
|
||||
asciiTexture.ReadPixels(_asciiImage);
|
||||
}
|
||||
|
||||
public void Update(double deltaTime)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
_framebuffer.Resize(Width, Height);
|
||||
_engine.Renderer.Resize(Width, Height);
|
||||
_consoleOutput.Resize(Width, Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
PresenterConsole/src/Program.cs
Normal file
12
PresenterConsole/src/Program.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
// See https://aka.ms/new-console-template for more information
|
||||
|
||||
using PresenterConsole;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var engine = new Engine.Engine(240, 135, (parEngine) => new ConsolePresenter(parEngine));
|
||||
engine.Run();
|
||||
}
|
||||
}
|
||||
95
PresenterConsole/src/Resource/ShaderResource.Designer.cs
generated
Normal file
95
PresenterConsole/src/Resource/ShaderResource.Designer.cs
generated
Normal file
@@ -0,0 +1,95 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace PresenterConsole.Resource {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class ShaderResource {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal ShaderResource() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PresenterConsole.src.Resource.ShaderResource", typeof(ShaderResource).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to // #type vertex
|
||||
///#version 460 core
|
||||
///
|
||||
///layout (location = 0) in vec2 aPos;
|
||||
///layout (location = 1) in vec2 aUV;
|
||||
///
|
||||
///layout (location = 0) out vec2 oUV;
|
||||
///
|
||||
///void main()
|
||||
///{
|
||||
/// oUV = aUV;
|
||||
/// gl_Position = vec4(aPos, 0.0, 1.0);
|
||||
///}
|
||||
///
|
||||
/// // #type fragment
|
||||
///#version 460 core
|
||||
///
|
||||
///precision highp float;
|
||||
///
|
||||
/// // All components are in the range [0…1], including hue.
|
||||
///vec3 rgb2hsv(vec3 c)
|
||||
///{
|
||||
/// vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
|
||||
/// vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
|
||||
/// vec4 [rest of string was truncated]";.
|
||||
/// </summary>
|
||||
internal static string Ascii {
|
||||
get {
|
||||
return ResourceManager.GetString("Ascii", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30
PresenterConsole/src/Resource/ShaderResource.resx
Normal file
30
PresenterConsole/src/Resource/ShaderResource.resx
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>1.3</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
|
||||
<data name="Ascii" type="System.Resources.ResXFileRef" xml:space="preserve">
|
||||
<value>../../assets/shader/ascii.shader;System.String, mscorlib, Version=4.0.0.0, Culture=neutral;utf-8</value>
|
||||
</data>
|
||||
</root>
|
||||
Reference in New Issue
Block a user