Rename Dagger to Voile.
This commit is contained in:
33
Voile/Source/Audio/AudioBackend.cs
Normal file
33
Voile/Source/Audio/AudioBackend.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
namespace Voile.Audio
|
||||
{
|
||||
public abstract class AudioBackend : IDisposable
|
||||
{
|
||||
public abstract void Initialize();
|
||||
public abstract void Update();
|
||||
public abstract void Shutdown();
|
||||
// BUS
|
||||
public abstract void CreateBus(string busName);
|
||||
public abstract void SetBusVolume(string busName, float volume);
|
||||
public abstract float GetBusVolume(string busName);
|
||||
|
||||
// SOUND
|
||||
public abstract void PlaySound(Sound sound, string bus = "Master", float pitch = 1.0f, float volume = 1.0f);
|
||||
public void PlaySound(Sound sound, string bus = "Master") => PlaySound(sound, bus, default, default);
|
||||
|
||||
public SoundInstance CreateInstance(Sound sound)
|
||||
{
|
||||
var instance = new SoundInstance(this, sound);
|
||||
return instance;
|
||||
}
|
||||
|
||||
// EFFECTS
|
||||
public abstract void AddBusEffect<T>(T effect, string bus = "Master") where T : AudioEffect;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
private LehmerRandom _random = new LehmerRandom();
|
||||
}
|
||||
}
|
||||
7
Voile/Source/Audio/AudioBus.cs
Normal file
7
Voile/Source/Audio/AudioBus.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Voile
|
||||
{
|
||||
public class AudioBus
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
9
Voile/Source/Audio/AudioEffect.cs
Normal file
9
Voile/Source/Audio/AudioEffect.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Voile
|
||||
{
|
||||
public class AudioEffect { }
|
||||
|
||||
public class AudioEffectReverb : AudioEffect
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
45
Voile/Source/Audio/DummyAudioBackend.cs
Normal file
45
Voile/Source/Audio/DummyAudioBackend.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
namespace Voile.Audio
|
||||
{
|
||||
public class DummyAudioBackend : AudioBackend
|
||||
{
|
||||
public override void AddBusEffect<T>(T effect, string bus = "Master")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
public override void CreateBus(string busName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
public override float GetBusVolume(string busName)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
public override void SetBusVolume(string busName, float volume)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
public override void PlaySound(Sound sound, string bus = "Master", float pitch = 1, float volume = 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
136
Voile/Source/Audio/FmodAudioBackend.cs
Normal file
136
Voile/Source/Audio/FmodAudioBackend.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
using FMOD;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Voile.Audio
|
||||
{
|
||||
public class FmodAudioBackend : AudioBackend
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
CreateSystem();
|
||||
_loadedSounds = new Dictionary<string, FMOD.Sound>();
|
||||
_channelGroups = new Dictionary<string, ChannelGroup>();
|
||||
|
||||
CreateBus("Master");
|
||||
}
|
||||
public override void Update() => _system.update();
|
||||
|
||||
public override void PlaySound(Sound sound, string bus = "Master", float pitch = 1, float volume = 1)
|
||||
{
|
||||
int channels = 0;
|
||||
|
||||
if (sound.Format == SoundFormat.Mono)
|
||||
{
|
||||
channels = 1;
|
||||
}
|
||||
else if (sound.Format == SoundFormat.Stereo)
|
||||
{
|
||||
channels = 2;
|
||||
}
|
||||
|
||||
var channel = PlaySoundFromBuffer(sound.Path, (int)sound.BufferSize, channels, sound.SampleRate, sound.Buffer, GetChannelGroup(bus));
|
||||
channel.setVolume(volume);
|
||||
channel.setPitch(pitch);
|
||||
}
|
||||
|
||||
public override void CreateBus(string busName)
|
||||
{
|
||||
ChannelGroup channelGroup;
|
||||
_system.createChannelGroup(busName, out channelGroup);
|
||||
_channelGroups.Add(busName, channelGroup);
|
||||
}
|
||||
|
||||
public override void SetBusVolume(string busName, float volume)
|
||||
{
|
||||
var channel = GetChannelGroup(busName);
|
||||
channel.setVolume(volume);
|
||||
}
|
||||
|
||||
public override float GetBusVolume(string busName)
|
||||
{
|
||||
float volume;
|
||||
GetChannelGroup(busName).getVolume(out volume);
|
||||
return volume;
|
||||
}
|
||||
|
||||
public override void AddBusEffect<T>(T effect, string bus = "Master")
|
||||
{
|
||||
var channelGroup = GetChannelGroup(bus);
|
||||
DSP dsp;
|
||||
|
||||
switch (effect)
|
||||
{
|
||||
case AudioEffectReverb:
|
||||
dsp = CreateReverbDsp(effect as AudioEffectReverb);
|
||||
break;
|
||||
default:
|
||||
_system.createDSPByType(DSP_TYPE.UNKNOWN, out dsp);
|
||||
break;
|
||||
}
|
||||
|
||||
channelGroup.addDSP(0, dsp);
|
||||
}
|
||||
|
||||
private DSP CreateReverbDsp(AudioEffectReverb effectReverb)
|
||||
{
|
||||
DSP dsp;
|
||||
_system.createDSPByType(DSP_TYPE.SFXREVERB, out dsp);
|
||||
return dsp;
|
||||
}
|
||||
|
||||
private void CreateSystem()
|
||||
{
|
||||
var result = FMOD.Factory.System_Create(out _system);
|
||||
_system.init(128, INITFLAGS.NORMAL, 0);
|
||||
}
|
||||
|
||||
private Channel PlaySoundFromBuffer(string path, int length, int channels, int sampleRate, byte[] buffer, ChannelGroup channelGroup)
|
||||
{
|
||||
Channel fmodChannel;
|
||||
FMOD.Sound fmodSound = IsLoaded(path) ? GetSoundFromLoaded(path) : CreateSound(length, channels, sampleRate, path, buffer);
|
||||
|
||||
_system.playSound(fmodSound, channelGroup, false, out fmodChannel);
|
||||
|
||||
return fmodChannel;
|
||||
}
|
||||
|
||||
|
||||
private FMOD.Sound GetSoundFromLoaded(string path) => _loadedSounds[path];
|
||||
private bool IsLoaded(string path) => _loadedSounds.ContainsKey(path);
|
||||
|
||||
private FMOD.Sound CreateSound(int length, int channels, int sampleRate, string path, byte[] buffer)
|
||||
{
|
||||
FMOD.Sound sound;
|
||||
CREATESOUNDEXINFO info = new CREATESOUNDEXINFO()
|
||||
{
|
||||
numchannels = channels,
|
||||
defaultfrequency = sampleRate,
|
||||
format = SOUND_FORMAT.PCM16,
|
||||
length = (uint)length,
|
||||
cbsize = Marshal.SizeOf(typeof(CREATESOUNDEXINFO))
|
||||
};
|
||||
|
||||
var result = _system.createSound(buffer, FMOD.MODE.OPENMEMORY | FMOD.MODE.OPENRAW | FMOD.MODE.CREATESAMPLE, ref info, out sound);
|
||||
AddToLoaded(path, sound);
|
||||
return sound;
|
||||
}
|
||||
|
||||
private void AddToLoaded(string path, FMOD.Sound sound) => _loadedSounds.Add(path, sound);
|
||||
|
||||
private ChannelGroup GetChannelGroup(string busName)
|
||||
{
|
||||
return _channelGroups[busName];
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private FMOD.System _system;
|
||||
|
||||
// TODO: use a different key for the dictionary, paths are not good :( (waste of memory lol)
|
||||
private Dictionary<string, FMOD.Sound> _loadedSounds;
|
||||
private Dictionary<string, ChannelGroup> _channelGroups;
|
||||
}
|
||||
}
|
||||
46
Voile/Source/Audio/SoundInstance.cs
Normal file
46
Voile/Source/Audio/SoundInstance.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
namespace Voile.Audio
|
||||
{
|
||||
public class SoundInstance
|
||||
{
|
||||
public SoundInstance(AudioBackend backend, Sound sound)
|
||||
{
|
||||
_backend = backend;
|
||||
_sound = sound;
|
||||
}
|
||||
|
||||
public SoundInstance PitchVariation(float min, float max)
|
||||
{
|
||||
var random = new LehmerRandom();
|
||||
_pitch = (float)random.NextDouble() * (max - min) + min;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SoundInstance VolumeVariation(float min, float max)
|
||||
{
|
||||
var random = new LehmerRandom();
|
||||
_volume = (float)random.NextDouble() * (max - min) + min;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SoundInstance OnBus(string bus = "Master")
|
||||
{
|
||||
_bus = bus;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void Play()
|
||||
{
|
||||
_backend.PlaySound(GetSound(), _bus, _pitch, _volume);
|
||||
}
|
||||
|
||||
protected virtual Sound GetSound()
|
||||
{
|
||||
return _sound;
|
||||
}
|
||||
|
||||
private readonly AudioBackend _backend;
|
||||
private readonly Sound _sound;
|
||||
private string _bus = "Master";
|
||||
private float _pitch, _volume = 1.0f;
|
||||
}
|
||||
}
|
||||
110
Voile/Source/Color.cs
Normal file
110
Voile/Source/Color.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
namespace Voile
|
||||
{
|
||||
// Based on https://github.com/ppr-game/PPR/blob/engine/PER.Util/src/Color.cs
|
||||
/// <summary>
|
||||
/// A record struct representing a color.
|
||||
/// </summary>
|
||||
public record struct Color
|
||||
{
|
||||
// TODO: add more HTML colors.
|
||||
public static Color AliceBlue = new(0xF0F8FF);
|
||||
public static Color AntiqueWhite = new(0xFAEBD7);
|
||||
public static Color Aqua = new(0x00FFFF);
|
||||
public static Color Aquamarine = new(0x7FFFD4);
|
||||
public static Color Azure = new(0xF0FFFF);
|
||||
public static Color Beige = new(0xF5F5DC);
|
||||
public static Color Bisque = new(0xFFE4C4);
|
||||
public static Color Black = new(0x000000);
|
||||
public static Color BlanchedAlmond = new(0xFFEBCD);
|
||||
public static Color Blue = new(0x0000FF);
|
||||
public static Color BlueViolet = new(0x8A2BE2);
|
||||
public static Color Brown = new(0xA52A2A);
|
||||
public static Color BurlyWood = new(0xDEB887);
|
||||
public static Color CadetBlue = new(0x5F9EA0);
|
||||
public static Color Chartreuse = new(0x7FFF00);
|
||||
public static Color Chocolate = new(0xD2691E);
|
||||
public static Color Coral = new(0xFF7F50);
|
||||
public static Color CornflowerBlue = new(0x6495ED);
|
||||
public static Color Cornsilk = new(0xFFF8DC);
|
||||
public static Color Crimson = new(0xDC143C);
|
||||
public static Color Cyan = new(0x00FFFF);
|
||||
public static Color DarkBlue = new(0x00008B);
|
||||
public static Color DarkCyan = new(0x008B8B);
|
||||
public static Color White = new(0xFFFFFF);
|
||||
public static Color Green = new(0x00FF00);
|
||||
public static Color Red = new(0xFF0000);
|
||||
|
||||
public float R { get; set; }
|
||||
public float G { get; set; }
|
||||
public float B { get; set; }
|
||||
public float A { get; set; }
|
||||
|
||||
public int Argb
|
||||
{
|
||||
get
|
||||
{
|
||||
int c = (ushort)Math.Round(A * 255f);
|
||||
c <<= 8;
|
||||
c |= (ushort)Math.Round(R * 255f);
|
||||
c <<= 8;
|
||||
c |= (ushort)Math.Round(G * 255f);
|
||||
c <<= 8;
|
||||
c |= (ushort)Math.Round(B * 255f);
|
||||
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
public Color(float r, float g, float b, float a)
|
||||
{
|
||||
R = r;
|
||||
G = g;
|
||||
B = b;
|
||||
A = a;
|
||||
}
|
||||
|
||||
public Color(byte r, byte g, byte b, byte a)
|
||||
{
|
||||
R = r / 255f;
|
||||
G = g / 255f;
|
||||
B = b / 255f;
|
||||
A = a / 255f;
|
||||
}
|
||||
|
||||
public Color(int hex)
|
||||
{
|
||||
A = 1.0f;
|
||||
B = (hex & 0xFF) / 255.0f;
|
||||
hex >>= 8;
|
||||
G = (hex & 0xFF) / 255.0f;
|
||||
hex >>= 8;
|
||||
R = (hex & 0xFF) / 255.0f;
|
||||
}
|
||||
|
||||
public Color Lightened(float amount)
|
||||
{
|
||||
var result = this;
|
||||
result.R = result.R + (1.0f - result.R) * amount;
|
||||
result.G = result.G + (1.0f - result.G) * amount;
|
||||
result.B = result.B + (1.0f - result.B) * amount;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Color Darkened(float amount)
|
||||
{
|
||||
var result = this;
|
||||
result.R = result.R * (1.0f - amount);
|
||||
result.G = result.G * (1.0f - amount);
|
||||
result.B = result.B * (1.0f - amount);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public System.Drawing.Color ToSystemColor()
|
||||
{
|
||||
var result = System.Drawing.Color.FromArgb(Argb);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Voile/Source/Extensions/Mat4Extensions.cs
Normal file
14
Voile/Source/Extensions/Mat4Extensions.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace Voile.Extensions
|
||||
{
|
||||
public static class Mat4Extensions
|
||||
{
|
||||
public static Matrix4x4 Scale(this Matrix4x4 mat, Vector2 scale)
|
||||
{
|
||||
|
||||
|
||||
return mat;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Voile/Source/Extensions/Vector2Extensions.cs
Normal file
12
Voile/Source/Extensions/Vector2Extensions.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace Voile.Extensions
|
||||
{
|
||||
public static class Vector2Extensions
|
||||
{
|
||||
public static Vector2 Lerp(this Vector2 a, Vector2 b, double t)
|
||||
{
|
||||
return new Vector2(MathUtils.Lerp(a.X, b.X, t), MathUtils.Lerp(a.Y, b.Y, t));
|
||||
}
|
||||
}
|
||||
}
|
||||
2506
Voile/Source/External/FastNoiseLite.cs
vendored
Normal file
2506
Voile/Source/External/FastNoiseLite.cs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
201
Voile/Source/External/LehmerRandom.cs
vendored
Normal file
201
Voile/Source/External/LehmerRandom.cs
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
/// <summary>
|
||||
/// https://github.com/YourRobotOverlord/LehmerRandom/blob/master/Lehmer/Random.cs
|
||||
/// Alternative to System.Random based on the Lehmer algorithm.
|
||||
/// </summary>
|
||||
public class LehmerRandom
|
||||
{
|
||||
// Stores the seed for the Next methods.
|
||||
private uint _seed;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Random class, using a time-dependent default seed value.
|
||||
/// </summary>
|
||||
public LehmerRandom()
|
||||
{
|
||||
_seed = (uint)System.Environment.TickCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Random class, using the specified seed value.
|
||||
/// </summary>
|
||||
/// <param name="seed"></param>
|
||||
public LehmerRandom(int seed)
|
||||
{
|
||||
_seed = (uint)seed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Random class, using the specified seed value.
|
||||
/// </summary>
|
||||
/// <param name="seed"></param>
|
||||
public LehmerRandom(uint seed)
|
||||
{
|
||||
_seed = seed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a non-negative random integer.
|
||||
/// </summary>
|
||||
/// <returns>Int32</returns>
|
||||
public int Next()
|
||||
{
|
||||
return GetNextInt(0, int.MaxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a non-negative random integer that is less than the specified maximum.
|
||||
/// </summary>
|
||||
/// <param name="maxValue">Int32</param>
|
||||
/// <returns>Int32</returns>
|
||||
public int Next(int maxValue)
|
||||
{
|
||||
return GetNextInt(0, maxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random integer that is within a specified range.
|
||||
/// </summary>
|
||||
/// <param name="minValue">Int32</param>
|
||||
/// <param name="maxValue">Int32</param>
|
||||
/// <returns>Int32</returns>
|
||||
public int Next(int minValue, int maxValue)
|
||||
{
|
||||
return GetNextInt(minValue, maxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random floating-point number that is greater than or equal to 0.0, and less than 1.0
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public double NextDouble()
|
||||
{
|
||||
return GetNextDouble(0.0, 1.0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a non-negative random floating-point number that is less than the specified maximum.
|
||||
/// </summary>
|
||||
/// <param name="maxValue"></param>
|
||||
/// <returns></returns>
|
||||
public double NextDouble(double maxValue)
|
||||
{
|
||||
return GetNextDouble(0.0, maxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random floating-point number that is within a specified range.
|
||||
/// </summary>
|
||||
/// <param name="minValue"></param>
|
||||
/// <param name="maxValue"></param>
|
||||
/// <returns></returns>
|
||||
public double NextDouble(double minValue, double maxValue)
|
||||
{
|
||||
return GetNextDouble(minValue, maxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills the elements of a specified array of bytes with random numbers.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The array to be filled with random numbers.</param>
|
||||
public void NextBytes(byte[] buffer)
|
||||
{
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
{
|
||||
buffer[i] = (byte)Next();
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a random int and sets the seed for the next pass.
|
||||
internal int GetNextInt(int minValue, int maxValue)
|
||||
{
|
||||
_seed = Rnd(_seed);
|
||||
return ConvertToIntRange(_seed, minValue, maxValue);
|
||||
}
|
||||
|
||||
// Returns a random double and sets the seed for the next pass.
|
||||
internal double GetNextDouble(double minValue, double maxValue)
|
||||
{
|
||||
_seed = Rnd(_seed);
|
||||
return ConvertToDoubleRange(_seed, minValue, maxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random integer that is within a specified range, using the specified seed value.
|
||||
/// </summary>
|
||||
/// <param name="seed"></param>
|
||||
/// <param name="minValue"></param>
|
||||
/// <param name="maxValue"></param>
|
||||
/// <returns></returns>
|
||||
public static int RndInt(uint seed, int minValue, int maxValue)
|
||||
{
|
||||
return GetInt(seed, minValue, maxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random integer that is within a specified range, using a time-dependent default seed value.
|
||||
/// </summary>
|
||||
/// <param name="minValue"></param>
|
||||
/// <param name="maxValue"></param>
|
||||
/// <returns></returns>
|
||||
public static int RndInt(int minValue, int maxValue)
|
||||
{
|
||||
return GetInt((uint)System.Environment.TickCount, minValue, maxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random double that is within a specified range, using the specified seed value.
|
||||
/// </summary>
|
||||
/// <param name="seed"></param>
|
||||
/// <param name="minValue"></param>
|
||||
/// <param name="maxValue"></param>
|
||||
/// <returns></returns>
|
||||
public static double RndDouble(uint seed, double minValue, double maxValue)
|
||||
{
|
||||
return GetDouble(seed, minValue, maxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random double that is within a specified range, using a time-dependent default seed value.
|
||||
/// </summary>
|
||||
/// <param name="minValue"></param>
|
||||
/// <param name="maxValue"></param>
|
||||
/// <returns></returns>
|
||||
public static double RndDouble(double minValue, double maxValue)
|
||||
{
|
||||
return GetDouble((uint)System.Environment.TickCount, minValue, maxValue);
|
||||
}
|
||||
|
||||
internal static int GetInt(uint seed, int minValue, int maxValue)
|
||||
{
|
||||
return ConvertToIntRange(Rnd(seed), minValue, maxValue);
|
||||
}
|
||||
|
||||
internal static double GetDouble(uint seed, double minValue, double maxValue)
|
||||
{
|
||||
return ConvertToDoubleRange(Rnd(seed), minValue, maxValue);
|
||||
}
|
||||
|
||||
// Converts uint to integer within the given range.
|
||||
internal static int ConvertToIntRange(uint val, int minValue, int maxValue)
|
||||
{
|
||||
return (int)(val % (maxValue - minValue) + minValue);
|
||||
}
|
||||
|
||||
// Converts uint to double within the given range.
|
||||
internal static double ConvertToDoubleRange(uint val, double minValue, double maxValue)
|
||||
{
|
||||
return (double)val / uint.MaxValue * (maxValue - minValue) + minValue;
|
||||
}
|
||||
|
||||
// Pseudo-random number generator based on the Lehmer Algorithm
|
||||
// and javidx9's implementation:
|
||||
// https://github.com/OneLoneCoder/olcPixelGameEngine/blob/master/Videos/OneLoneCoder_PGE_ProcGen_Universe.cpp
|
||||
internal static uint Rnd(uint seed)
|
||||
{
|
||||
seed += 0xe120fc15;
|
||||
ulong tmp = (ulong)seed * 0x4a39b70d;
|
||||
uint m1 = (uint)((tmp >> 32) ^ tmp);
|
||||
tmp = (ulong)m1 * 0x12fad5c9;
|
||||
return (uint)((tmp >> 32) ^ tmp);
|
||||
}
|
||||
}
|
||||
41
Voile/Source/Game.cs
Normal file
41
Voile/Source/Game.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
namespace Voile
|
||||
{
|
||||
public abstract class Game
|
||||
{
|
||||
public abstract string ResourceRoot { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Starts the game application.
|
||||
/// This involves initializing the required subsystems, loading resources from the disk, and then preparing the subsystems for running in the main loop.
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
Initialize();
|
||||
LoadResources();
|
||||
Ready();
|
||||
Run();
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when it's time to initialize the subsystems.
|
||||
/// </summary>
|
||||
public abstract void Initialize();
|
||||
/// <summary>
|
||||
/// Called when it's time to load the application's resources, such as images or sounds.
|
||||
/// </summary>
|
||||
protected abstract void LoadResources();
|
||||
/// <summary>
|
||||
/// Called when it's safe to manipulate with the resources or/and systems.
|
||||
/// </summary>
|
||||
protected abstract void Ready();
|
||||
/// <summary>
|
||||
/// Called when everything has been readied to start the main loop.
|
||||
/// </summary>
|
||||
protected abstract void Run();
|
||||
/// <summary>
|
||||
/// Called when the application quits and it's safe to clean up.
|
||||
/// </summary>
|
||||
public abstract void Shutdown();
|
||||
}
|
||||
}
|
||||
36
Voile/Source/InputAction.cs
Normal file
36
Voile/Source/InputAction.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
namespace Voile
|
||||
{
|
||||
public abstract class InputAction
|
||||
{
|
||||
public abstract bool IsDown(InputHandler inputHandler);
|
||||
public abstract bool IsPressed(InputHandler inputHandler);
|
||||
public abstract bool IsReleased(InputHandler inputHandler);
|
||||
}
|
||||
|
||||
public class KeyInputAction : InputAction
|
||||
{
|
||||
public KeyboardKey Key => _keyboardKey;
|
||||
|
||||
public KeyInputAction(KeyboardKey keyboardKey)
|
||||
{
|
||||
_keyboardKey = keyboardKey;
|
||||
}
|
||||
public override bool IsDown(InputHandler inputHandler)
|
||||
{
|
||||
return inputHandler.IsKeyboardKeyDown(_keyboardKey);
|
||||
}
|
||||
|
||||
public override bool IsPressed(InputHandler inputHandler)
|
||||
{
|
||||
return inputHandler.KeyboardKeyJustPressed(_keyboardKey);
|
||||
}
|
||||
|
||||
public override bool IsReleased(InputHandler inputHandler)
|
||||
{
|
||||
return inputHandler.KeyboardKeyJustReleased(_keyboardKey);
|
||||
}
|
||||
|
||||
|
||||
private KeyboardKey _keyboardKey;
|
||||
}
|
||||
}
|
||||
203
Voile/Source/InputHandler.cs
Normal file
203
Voile/Source/InputHandler.cs
Normal file
@@ -0,0 +1,203 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using Voile.Utils;
|
||||
|
||||
namespace Voile
|
||||
{
|
||||
public abstract class InputHandler
|
||||
{
|
||||
public InputHandler()
|
||||
{
|
||||
inputMappings = new Dictionary<string, IEnumerable<InputAction>>();
|
||||
CreateDefaultMappings();
|
||||
}
|
||||
public Action OnInput;
|
||||
public bool Handled { get => _handled; set => _handled = value; }
|
||||
public abstract bool IsKeyboardKeyDown(KeyboardKey key);
|
||||
public abstract bool KeyboardKeyJustPressed(KeyboardKey key);
|
||||
public abstract bool KeyboardKeyJustReleased(KeyboardKey key);
|
||||
public abstract Vector2 GetInputDirection(KeyboardKey leftKey, KeyboardKey rightKey, KeyboardKey upKey, KeyboardKey downKey);
|
||||
public abstract Vector2 GetInputDirection(string leftAction, string rightAction, string upAction, string downAction);
|
||||
public abstract int GetCharPressed();
|
||||
|
||||
public abstract bool IsActionDown(string action);
|
||||
public abstract bool IsActionPressed(string action);
|
||||
public abstract bool IsActionReleased(string action);
|
||||
|
||||
public abstract bool IsMouseButtonDown(MouseButton button);
|
||||
public abstract float GetMouseWheelMovement();
|
||||
public abstract void SetMousePosition(Vector2 position);
|
||||
public abstract Vector2 GetMousePosition();
|
||||
public abstract void HideCursor();
|
||||
public abstract void ShowCursor();
|
||||
public abstract bool IsCursorHidden();
|
||||
|
||||
public void SetAsHandled() => _handled = true;
|
||||
|
||||
public void AddInputMapping(string actionName, IEnumerable<InputAction> inputActions)
|
||||
{
|
||||
inputMappings.Add(actionName, inputActions);
|
||||
}
|
||||
|
||||
private void CreateDefaultMappings()
|
||||
{
|
||||
AddInputMapping("up", new InputAction[] {
|
||||
new KeyInputAction(KeyboardKey.W),
|
||||
new KeyInputAction(KeyboardKey.Up),
|
||||
});
|
||||
AddInputMapping("down", new InputAction[] {
|
||||
new KeyInputAction(KeyboardKey.S),
|
||||
new KeyInputAction(KeyboardKey.Down),
|
||||
});
|
||||
AddInputMapping("left", new InputAction[] {
|
||||
new KeyInputAction(KeyboardKey.A),
|
||||
new KeyInputAction(KeyboardKey.Left),
|
||||
});
|
||||
AddInputMapping("right", new InputAction[] {
|
||||
new KeyInputAction(KeyboardKey.D),
|
||||
new KeyInputAction(KeyboardKey.Right),
|
||||
});
|
||||
}
|
||||
|
||||
protected bool TryGetInputMappings(string forAction, [NotNullWhen(true)] out IEnumerable<InputAction>? inputActions)
|
||||
{
|
||||
var contains = inputMappings.ContainsKey(forAction);
|
||||
inputActions = null;
|
||||
|
||||
if (!contains)
|
||||
{
|
||||
_logger.Error($"The action \"{forAction}\" is not present in the input mappings!");
|
||||
return false;
|
||||
}
|
||||
|
||||
inputActions = inputMappings[forAction];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool _handled;
|
||||
protected Dictionary<string, IEnumerable<InputAction>> inputMappings;
|
||||
|
||||
private Logger _logger = new(nameof(InputHandler));
|
||||
}
|
||||
|
||||
public enum KeyboardKey
|
||||
{
|
||||
Null = 0,
|
||||
Back = 4,
|
||||
VolumeUp = 24,
|
||||
VolumeDown = 25,
|
||||
Spacebar = 32,
|
||||
Apostrophe = 39,
|
||||
Comma = 44,
|
||||
Minus = 45,
|
||||
Period = 46,
|
||||
Slash = 47,
|
||||
Zero = 48,
|
||||
One = 49,
|
||||
Two = 50,
|
||||
Three = 51,
|
||||
Four = 52,
|
||||
Five = 53,
|
||||
Six = 54,
|
||||
Seven = 55,
|
||||
Eight = 56,
|
||||
Nine = 57,
|
||||
Semicolon = 59,
|
||||
Equal = 61,
|
||||
A = 65,
|
||||
B = 66,
|
||||
C = 67,
|
||||
D = 68,
|
||||
E = 69,
|
||||
F = 70,
|
||||
G = 71,
|
||||
H = 72,
|
||||
I = 73,
|
||||
J = 74,
|
||||
K = 75,
|
||||
L = 76,
|
||||
M = 77,
|
||||
N = 78,
|
||||
O = 79,
|
||||
P = 80,
|
||||
Q = 81,
|
||||
R = 82,
|
||||
Menu = 82,
|
||||
S = 83,
|
||||
T = 84,
|
||||
U = 85,
|
||||
V = 86,
|
||||
W = 87,
|
||||
X = 88,
|
||||
Y = 89,
|
||||
Z = 90,
|
||||
LeftBracket = 91,
|
||||
Backslash = 92,
|
||||
RightBracket = 93,
|
||||
Grave = 96,
|
||||
Escape = 256,
|
||||
Enter = 257,
|
||||
Tab = 258,
|
||||
Backspace = 259,
|
||||
Insert = 260,
|
||||
Delete = 261,
|
||||
Right = 262,
|
||||
Left = 263,
|
||||
Down = 264,
|
||||
Up = 265,
|
||||
PageUp = 266,
|
||||
PageDown = 267,
|
||||
Home = 268,
|
||||
End = 269,
|
||||
CapsLock = 280,
|
||||
ScrollLock = 281,
|
||||
NumLock = 282,
|
||||
PrintScreen = 283,
|
||||
Pause = 284,
|
||||
F1 = 290,
|
||||
F2 = 291,
|
||||
F3 = 292,
|
||||
F4 = 293,
|
||||
F5 = 294,
|
||||
F6 = 295,
|
||||
F7 = 296,
|
||||
F8 = 297,
|
||||
F9 = 298,
|
||||
F10 = 299,
|
||||
F11 = 300,
|
||||
F12 = 301,
|
||||
KP0 = 320,
|
||||
KP1 = 321,
|
||||
KP2 = 322,
|
||||
KP3 = 323,
|
||||
KP4 = 324,
|
||||
KP5 = 325,
|
||||
KP6 = 326,
|
||||
KP7 = 327,
|
||||
KP8 = 328,
|
||||
KP9 = 329,
|
||||
KPDecimal = 330,
|
||||
KPDivide = 331,
|
||||
KPMultiply = 332,
|
||||
KPSubstract = 333,
|
||||
KPAdd = 334,
|
||||
KPEnter = 335,
|
||||
KPEqual = 336,
|
||||
LeftShift = 340,
|
||||
LeftControl = 341,
|
||||
LeftAlt = 342,
|
||||
LeftSuper = 343,
|
||||
RightShift = 344,
|
||||
RightControl = 345,
|
||||
RightAlt = 346,
|
||||
RightSuper = 347,
|
||||
KBMenu = 348
|
||||
}
|
||||
public enum MouseButton
|
||||
{
|
||||
Left = 0,
|
||||
Right = 1,
|
||||
Middle = 2,
|
||||
}
|
||||
}
|
||||
130
Voile/Source/RaylibInputHandler.cs
Normal file
130
Voile/Source/RaylibInputHandler.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System.Numerics;
|
||||
using Raylib_cs;
|
||||
|
||||
namespace Voile
|
||||
{
|
||||
public class RaylibInputHandler : InputHandler
|
||||
{
|
||||
public override int GetCharPressed()
|
||||
{
|
||||
return Raylib.GetCharPressed();
|
||||
}
|
||||
|
||||
public override Vector2 GetInputDirection(KeyboardKey leftKey, KeyboardKey rightKey, KeyboardKey upKey, KeyboardKey downKey)
|
||||
{
|
||||
Vector2 dir = Vector2.Zero;
|
||||
|
||||
if (IsKeyboardKeyDown(leftKey))
|
||||
dir += new Vector2(-1, 0);
|
||||
if (IsKeyboardKeyDown(rightKey))
|
||||
dir += new Vector2(1, 0);
|
||||
if (IsKeyboardKeyDown(upKey))
|
||||
dir += new Vector2(0, -1);
|
||||
if (IsKeyboardKeyDown(downKey))
|
||||
dir += new Vector2(0, 1);
|
||||
|
||||
return dir == Vector2.Zero ? Vector2.Zero : Vector2.Normalize(dir);
|
||||
}
|
||||
|
||||
public override Vector2 GetInputDirection(string leftAction, string rightAction, string upAction, string downAction)
|
||||
{
|
||||
Vector2 dir = Vector2.Zero;
|
||||
|
||||
if (IsActionDown(leftAction))
|
||||
dir += new Vector2(-1, 0);
|
||||
if (IsActionDown(rightAction))
|
||||
dir += new Vector2(1, 0);
|
||||
if (IsActionDown(upAction))
|
||||
dir += new Vector2(0, -1);
|
||||
if (IsActionDown(downAction))
|
||||
dir += new Vector2(0, 1);
|
||||
|
||||
|
||||
return dir == Vector2.Zero ? Vector2.Zero : Vector2.Normalize(dir);
|
||||
}
|
||||
|
||||
public override Vector2 GetMousePosition()
|
||||
{
|
||||
return Raylib.GetMousePosition();
|
||||
}
|
||||
|
||||
public override float GetMouseWheelMovement()
|
||||
{
|
||||
return Raylib.GetMouseWheelMove();
|
||||
}
|
||||
|
||||
public override void HideCursor()
|
||||
{
|
||||
Raylib.HideCursor();
|
||||
}
|
||||
|
||||
public override bool IsActionDown(string action)
|
||||
{
|
||||
IEnumerable<InputAction>? mappings;
|
||||
|
||||
if (TryGetInputMappings(action, out mappings))
|
||||
{
|
||||
foreach (InputAction inputAction in mappings)
|
||||
if (inputAction.IsDown(this)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool IsActionPressed(string action)
|
||||
{
|
||||
IEnumerable<InputAction>? mappings;
|
||||
|
||||
if (TryGetInputMappings(action, out mappings))
|
||||
{
|
||||
foreach (InputAction inputAction in mappings)
|
||||
if (inputAction.IsPressed(this)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool IsActionReleased(string action)
|
||||
{
|
||||
IEnumerable<InputAction>? mappings;
|
||||
|
||||
if (TryGetInputMappings(action, out mappings))
|
||||
{
|
||||
foreach (InputAction inputAction in mappings)
|
||||
if (inputAction.IsReleased(this)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool IsKeyboardKeyDown(KeyboardKey key)
|
||||
{
|
||||
Raylib_cs.KeyboardKey rayKey = (Raylib_cs.KeyboardKey)key;
|
||||
OnInput?.Invoke();
|
||||
return Raylib.IsKeyDown(rayKey);
|
||||
}
|
||||
|
||||
public override bool IsMouseButtonDown(MouseButton button)
|
||||
{
|
||||
return Raylib.IsMouseButtonDown((Raylib_cs.MouseButton)button);
|
||||
}
|
||||
|
||||
public override bool KeyboardKeyJustPressed(KeyboardKey key)
|
||||
{
|
||||
Raylib_cs.KeyboardKey rayKey = (Raylib_cs.KeyboardKey)key;
|
||||
return Raylib.IsKeyPressed(rayKey);
|
||||
}
|
||||
|
||||
public override bool KeyboardKeyJustReleased(KeyboardKey key)
|
||||
{
|
||||
return Raylib.IsKeyReleased((Raylib_cs.KeyboardKey)key);
|
||||
}
|
||||
|
||||
public override void SetMousePosition(Vector2 position)
|
||||
{
|
||||
Raylib.SetMousePosition((int)position.X, (int)position.Y);
|
||||
}
|
||||
public override void ShowCursor() => Raylib.ShowCursor();
|
||||
public override bool IsCursorHidden() => Raylib.IsCursorHidden();
|
||||
}
|
||||
}
|
||||
49
Voile/Source/Rendering/ColorRectShader.cs
Normal file
49
Voile/Source/Rendering/ColorRectShader.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
namespace Voile.Rendering
|
||||
{
|
||||
public class ColorRectShader : Shader
|
||||
{
|
||||
public override string FragmentSource => @"
|
||||
#version 450
|
||||
layout(location = 0) out vec4 fsout_Color;
|
||||
layout (set = 0, binding = 0) uniform Uniforms
|
||||
{
|
||||
vec4 color;
|
||||
};
|
||||
|
||||
void main()
|
||||
{
|
||||
fsout_Color = color;
|
||||
}
|
||||
";
|
||||
|
||||
public override string VertexSource => @"
|
||||
#version 450
|
||||
layout(location = 0) in vec2 Position;
|
||||
|
||||
// uniform MatrixBlock
|
||||
// {
|
||||
// // mat4 model = mat4();
|
||||
// // mat4 view;
|
||||
// // mat4 proj;
|
||||
// mat4 transform = mat4(1.0, 1.0, 1.0, 1.0,
|
||||
// 1.0, 1.0, 1.0, 1.0,
|
||||
// 1.0, 1.0, 1.0, 1.0,
|
||||
// 1.0, 1.0, 1.0, 1.0);
|
||||
// };
|
||||
|
||||
// mat4 transform = mat4(1.0, 1.0, 0.0, 1.0,
|
||||
// 0.0, 1.0, 1.0, 1.0,
|
||||
// 0.0, 0.0, 1.0, 1.0,
|
||||
// 0.0, 0.0, 0.0, 1.0);
|
||||
|
||||
mat4 transform = mat4(1.0);
|
||||
|
||||
void main()
|
||||
{
|
||||
// gl_Position = proj * view * model * vec4(Position, 0, 1);
|
||||
// gl_Position = vec4(Position, 0, 1);
|
||||
gl_Position = transform * vec4(Position, 0, 1);
|
||||
}";
|
||||
|
||||
}
|
||||
}
|
||||
298
Voile/Source/Rendering/RaylibRenderer.cs
Normal file
298
Voile/Source/Rendering/RaylibRenderer.cs
Normal file
@@ -0,0 +1,298 @@
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Raylib_cs;
|
||||
|
||||
namespace Voile.Rendering
|
||||
{
|
||||
public class RaylibRenderer : Renderer
|
||||
{
|
||||
public override bool ShouldRun => !WindowShouldClose();
|
||||
public override Vector2 WindowSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Vector2(Raylib.GetScreenWidth(), Raylib.GetScreenHeight());
|
||||
}
|
||||
set
|
||||
{
|
||||
Raylib.SetWindowSize((int)value.X, (int)value.Y);
|
||||
}
|
||||
}
|
||||
public override Vector2 MonitorSize => new Vector2(GetMonitorWidth(GetCurrentMonitor()), GetMonitorHeight(GetCurrentMonitor()));
|
||||
|
||||
public override string WindowTitle
|
||||
{
|
||||
get => _windowTitle; set
|
||||
{
|
||||
SetWindowTitle(value);
|
||||
}
|
||||
}
|
||||
|
||||
public override int TargetFps { get => _targetFps; set => SetTargetFps(value); }
|
||||
public override bool VSync { get => _vsync; set => SetWindowVSync(value); }
|
||||
public override bool Fullscreen { get => _fullscreen; set => SetFullscreen(value); }
|
||||
|
||||
public override void CreateWindow(WindowSettings windowSettings)
|
||||
{
|
||||
Raylib.SetTraceLogLevel(TraceLogLevel.LOG_NONE);
|
||||
|
||||
_defaultWindowSettings = windowSettings;
|
||||
|
||||
_windowSize = windowSettings.Size;
|
||||
ConfigFlags windowFlags = 0;
|
||||
windowFlags |= windowSettings.Resizable ? ConfigFlags.FLAG_WINDOW_RESIZABLE : 0;
|
||||
|
||||
_windowTitle = windowSettings.Title;
|
||||
|
||||
if (_fullscreen)
|
||||
{
|
||||
var monitorSize = MonitorSize;
|
||||
Raylib.InitWindow((int)monitorSize.X, (int)monitorSize.Y, windowSettings.Title);
|
||||
Fullscreen = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Raylib.InitWindow((int)_windowSize.X, (int)_windowSize.Y, windowSettings.Title);
|
||||
}
|
||||
|
||||
// Raylib.SetWindowState(windowFlags);
|
||||
}
|
||||
|
||||
protected override void SetWindowTitle(string title)
|
||||
{
|
||||
Raylib.SetWindowTitle(title);
|
||||
}
|
||||
|
||||
protected override void SetWindowVSync(bool value)
|
||||
{
|
||||
_vsync = value;
|
||||
|
||||
// TODO: implement VSync toggle for Raylib.
|
||||
}
|
||||
|
||||
protected override void SetTargetFps(int fps)
|
||||
{
|
||||
_targetFps = fps;
|
||||
Raylib.SetTargetFPS(fps);
|
||||
}
|
||||
|
||||
protected override bool WindowShouldClose()
|
||||
{
|
||||
return Raylib.WindowShouldClose();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
Raylib.CloseWindow();
|
||||
}
|
||||
|
||||
public override void BeginFrame()
|
||||
{
|
||||
Raylib.BeginDrawing();
|
||||
}
|
||||
|
||||
public override void EndFrame()
|
||||
{
|
||||
Raylib.EndDrawing();
|
||||
}
|
||||
|
||||
public override void BeginCamera2d(Vector2 offset, Vector2 target, float rotation, float zoom)
|
||||
{
|
||||
var camera = new Camera2D(offset, target, rotation, zoom);
|
||||
Raylib.BeginMode2D(camera);
|
||||
}
|
||||
|
||||
public override void EndCamera2d()
|
||||
{
|
||||
Raylib.EndMode2D();
|
||||
}
|
||||
|
||||
public override void ClearBackground(Color color)
|
||||
{
|
||||
Raylib.ClearBackground(VoileColorToRaylibColor(color));
|
||||
}
|
||||
|
||||
protected override double GetFrameTime()
|
||||
{
|
||||
return (double)Raylib.GetFrameTime();
|
||||
}
|
||||
|
||||
public override void DrawCircle(float radius, Color color)
|
||||
{
|
||||
Raylib.DrawCircle((int)transformPosition.X, (int)transformPosition.Y, radius, VoileColorToRaylibColor(color));
|
||||
}
|
||||
|
||||
public override void DrawTexture(Texture2d texture, Color tint)
|
||||
{
|
||||
if (texture.Handle == -1)
|
||||
{
|
||||
LoadTexture(texture);
|
||||
}
|
||||
|
||||
Raylib.DrawTextureV(_texturePool[texture.Handle], transformPosition, VoileColorToRaylibColor(tint));
|
||||
}
|
||||
|
||||
public override void DrawRectangle(Vector2 size, Color color)
|
||||
{
|
||||
Raylib.DrawRectanglePro(new Rectangle()
|
||||
{
|
||||
x = transformPosition.X,
|
||||
y = transformPosition.Y,
|
||||
width = size.X,
|
||||
height = size.Y
|
||||
}, transformOffset, transformRotation, VoileColorToRaylibColor(color));
|
||||
}
|
||||
|
||||
public override void DrawDebugText(string text, int fontSize, Color color)
|
||||
{
|
||||
Raylib.DrawText(text, (int)transformPosition.X, (int)transformPosition.Y, fontSize, VoileColorToRaylibColor(color));
|
||||
}
|
||||
|
||||
public override void DrawSdfText(string text, int fontSize, Color color)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Initialize(RendererSettings settings)
|
||||
{
|
||||
_targetFps = settings.TargetFps;
|
||||
|
||||
ConfigFlags flags = 0;
|
||||
|
||||
// MSAA
|
||||
flags |= settings.Msaa == Msaa.Msaa4x ? ConfigFlags.FLAG_MSAA_4X_HINT : 0;
|
||||
|
||||
// VSync
|
||||
flags |= settings.UseVSync ? ConfigFlags.FLAG_VSYNC_HINT : 0;
|
||||
|
||||
_fullscreen = settings.Fullscreen;
|
||||
|
||||
_defaultFlags = flags;
|
||||
|
||||
Raylib.SetConfigFlags(flags);
|
||||
}
|
||||
|
||||
private void SetFullscreen(bool fullscreen)
|
||||
{
|
||||
// var flags = _defaultFlags;
|
||||
// if (fullscreen && !Raylib.IsWindowFullscreen())
|
||||
// {
|
||||
// WindowSize = MonitorSize;
|
||||
// Raylib.ToggleFullscreen();
|
||||
// }
|
||||
// else if (Raylib.IsWindowFullscreen())
|
||||
// {
|
||||
// // Raylib.ToggleFullscreen();
|
||||
// WindowSize = _windowSize;
|
||||
// }
|
||||
|
||||
_fullscreen = fullscreen;
|
||||
}
|
||||
|
||||
// TODO
|
||||
public override void SetTransform(Matrix4x4 transform) { }
|
||||
|
||||
public override void CreateAndInitialize(WindowSettings windowSettings, RendererSettings renderSettings)
|
||||
{
|
||||
Initialize(renderSettings);
|
||||
CreateWindow(windowSettings);
|
||||
}
|
||||
|
||||
private Raylib_cs.Color VoileColorToRaylibColor(Color color)
|
||||
{
|
||||
return new Raylib_cs.Color { r = (byte)Math.Round(color.R * 255f), g = (byte)Math.Round(color.G * 255f), b = (byte)Math.Round(color.B * 255f), a = (byte)Math.Round(color.A * 255f) };
|
||||
}
|
||||
|
||||
public override void DrawText(Font font, string text, Color color)
|
||||
{
|
||||
if (font.Handle == -1)
|
||||
{
|
||||
LoadFont(font);
|
||||
}
|
||||
|
||||
var rayFont = _fontPool[font.Handle];
|
||||
Raylib.DrawTextPro(rayFont, text, transformPosition, transformOffset, transformRotation, font.Size, 0.0f, VoileColorToRaylibColor(color));
|
||||
}
|
||||
|
||||
protected override int GetMonitorWidth(int monitorId)
|
||||
{
|
||||
return Raylib.GetMonitorWidth(monitorId);
|
||||
}
|
||||
|
||||
protected override int GetMonitorHeight(int monitorId)
|
||||
{
|
||||
return Raylib.GetMonitorHeight(monitorId);
|
||||
}
|
||||
|
||||
protected override int GetCurrentMonitor()
|
||||
{
|
||||
return Raylib.GetCurrentMonitor();
|
||||
}
|
||||
|
||||
private unsafe void LoadFont(Font font)
|
||||
{
|
||||
Raylib_cs.Font fontRay;
|
||||
|
||||
ReadOnlySpan<char> ext = ".ttf"; // TODO: don't use a hardcoded extension.
|
||||
Span<byte> extBytes = new byte[Encoding.Default.GetByteCount(ext) + 1];
|
||||
Encoding.Default.GetBytes(ext, extBytes);
|
||||
int fontChars = 2048; // TODO: control this dynamically to not load the entire font.
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* extP = extBytes)
|
||||
{
|
||||
fixed (byte* bufferP = font.Buffer)
|
||||
{
|
||||
fontRay = Raylib.LoadFontFromMemory((sbyte*)extP, bufferP, (int)font.BufferSize, font.Size, null, fontChars);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Raylib.GenTextureMipmaps(ref fontRay.texture);
|
||||
Raylib.SetTextureFilter(fontRay.texture, TextureFilter.TEXTURE_FILTER_BILINEAR);
|
||||
|
||||
_fontPool.Add(fontRay);
|
||||
|
||||
font.Handle = _fontPool.Count - 1;
|
||||
}
|
||||
|
||||
private void LoadTexture(Texture2d texture)
|
||||
{
|
||||
Image image = new();
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (void* dataPtr = texture.Buffer)
|
||||
{
|
||||
image.data = dataPtr;
|
||||
}
|
||||
}
|
||||
|
||||
image.width = texture.Width;
|
||||
image.height = texture.Height;
|
||||
image.mipmaps = texture.Mipmaps;
|
||||
image.format = (PixelFormat)texture.Format;
|
||||
|
||||
Texture2D rayTexture;
|
||||
|
||||
rayTexture = Raylib.LoadTextureFromImage(image);
|
||||
|
||||
_texturePool.Add(rayTexture);
|
||||
|
||||
texture.Handle = _texturePool.Count - 1;
|
||||
}
|
||||
|
||||
|
||||
private List<Texture2D> _texturePool = new();
|
||||
private List<Raylib_cs.Font> _fontPool = new();
|
||||
private Vector2 _windowSize;
|
||||
private bool _vsync;
|
||||
private string _windowTitle = string.Empty;
|
||||
private int _targetFps;
|
||||
private bool _fullscreen;
|
||||
private WindowSettings _defaultWindowSettings;
|
||||
private ConfigFlags _defaultFlags;
|
||||
}
|
||||
}
|
||||
183
Voile/Source/Rendering/Renderer.cs
Normal file
183
Voile/Source/Rendering/Renderer.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace Voile.Rendering
|
||||
{
|
||||
/// <summary>
|
||||
/// An abstract class representing the graphics renderer.
|
||||
/// </summary>
|
||||
public abstract class Renderer
|
||||
{
|
||||
// INIT
|
||||
/// <summary>
|
||||
/// Creates the renderer window and initializes internal resources.
|
||||
/// </summary>
|
||||
/// <param name="windowSettings">Settings for the rendering window.</param>
|
||||
/// <param name="renderSettings">Rendering settings.</param>
|
||||
public abstract void CreateAndInitialize(WindowSettings windowSettings, RendererSettings renderSettings);
|
||||
/// <summary>
|
||||
/// Initializes internal resources. Should be called before other methods.
|
||||
/// </summary>
|
||||
/// <param name="settings">Rendering settings.</param>
|
||||
public abstract void Initialize(RendererSettings settings);
|
||||
|
||||
// UTIL
|
||||
/// <summary>
|
||||
/// Indicates if the renderer will render the next frame.
|
||||
/// </summary>
|
||||
public abstract bool ShouldRun { get; }
|
||||
/// <summary>
|
||||
/// Target frames per second for rendering.
|
||||
/// </summary>
|
||||
public abstract int TargetFps { get; set; }
|
||||
public abstract bool VSync { get; set; }
|
||||
public double FrameTime => GetFrameTime();
|
||||
|
||||
// WINDOW
|
||||
/// <summary>
|
||||
/// The size of the render window.
|
||||
/// </summary>
|
||||
public abstract Vector2 WindowSize { get; set; }
|
||||
|
||||
public abstract string WindowTitle { get; set; }
|
||||
public abstract bool Fullscreen { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Active monitor's size.
|
||||
/// </summary>
|
||||
public abstract Vector2 MonitorSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a window.
|
||||
/// </summary>
|
||||
/// <param name="windowSettings">Window settings to use to create the window.</param>
|
||||
public abstract void CreateWindow(WindowSettings windowSettings);
|
||||
protected abstract void SetWindowTitle(string title);
|
||||
protected abstract void SetWindowVSync(bool value);
|
||||
protected abstract void SetTargetFps(int fps);
|
||||
protected abstract double GetFrameTime();
|
||||
protected abstract int GetMonitorWidth(int monitorId);
|
||||
protected abstract int GetMonitorHeight(int monitorId);
|
||||
protected abstract int GetCurrentMonitor();
|
||||
protected abstract bool WindowShouldClose();
|
||||
public abstract void Shutdown();
|
||||
|
||||
// DRAWING
|
||||
/// <summary>
|
||||
/// Prepares the renderer for drawing the next frame.
|
||||
/// </summary>
|
||||
public abstract void BeginFrame();
|
||||
/// <summary>
|
||||
/// Ends rendering of the frame.
|
||||
/// </summary>
|
||||
public abstract void EndFrame();
|
||||
|
||||
public abstract void BeginCamera2d(Vector2 offset, Vector2 target, float rotation, float zoom);
|
||||
public abstract void EndCamera2d();
|
||||
|
||||
/// <summary>
|
||||
/// Clears the render canvas and sets a background color.
|
||||
/// </summary>
|
||||
/// <param name="color">Background color.</param>
|
||||
public abstract void ClearBackground(Color color);
|
||||
|
||||
public void ResetTransform()
|
||||
{
|
||||
transformPosition = Vector2.Zero;
|
||||
transformOffset = Vector2.Zero;
|
||||
transformRotation = 0.0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets transforms for the next draw operation.
|
||||
/// </summary>
|
||||
/// <param name="position">Global transform position.</param>
|
||||
/// <param name="offset">Local offset point around which shapes will rotate.</param>
|
||||
/// <param name="rotation">Rotation.</param>
|
||||
public void SetTransform(Vector2 position, Vector2 offset, float rotation = 0.0f)
|
||||
{
|
||||
transformPosition = position;
|
||||
transformOffset = offset;
|
||||
transformRotation = rotation;
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets the transform for the next draw operation.
|
||||
/// </summary>
|
||||
/// <param name="transform">Transform matrix.</param>
|
||||
public abstract void SetTransform(Matrix4x4 transform);
|
||||
/// <summary>
|
||||
/// Draws a filled circle.
|
||||
/// </summary>
|
||||
/// <param name="radius">Radius of a circle.</param>
|
||||
/// <param name="color">Fill color.</param>
|
||||
public abstract void DrawCircle(float radius, Color color);
|
||||
/// <summary>
|
||||
/// Draws a filled rectangle.
|
||||
/// </summary>
|
||||
/// <param name="size">Rectangle size.</param>
|
||||
/// <param name="color">Fill color.</param>
|
||||
public abstract void DrawRectangle(Vector2 size, Color color);
|
||||
/// <summary>
|
||||
/// Draws a debug text with a default font.
|
||||
/// </summary>
|
||||
/// <param name="position"></param>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="fontSize"></param>
|
||||
/// <param name="color"></param>
|
||||
public abstract void DrawDebugText(string text, int fontSize, Color color);
|
||||
/// <summary>
|
||||
/// Draws text using a signed distance field font atlas.
|
||||
/// </summary>
|
||||
/// <param name="text">Text to draw.</param>
|
||||
/// <param name="fontSize">Size of the font.</param>
|
||||
/// <param name="color">Color of the text.</param>
|
||||
public abstract void DrawSdfText(string text, int fontSize, Color color);
|
||||
|
||||
public abstract void DrawText(Font font, string text, Color color);
|
||||
|
||||
/// <summary>
|
||||
/// Draws the texture.
|
||||
/// </summary>
|
||||
/// <param name="id">Texture to draw.</param>
|
||||
/// <param name="tint">Texture tint.</param>
|
||||
public abstract void DrawTexture(Texture2d texture, Color tint);
|
||||
|
||||
protected Vector2 transformPosition, transformOffset;
|
||||
protected float transformRotation;
|
||||
}
|
||||
|
||||
public enum Msaa
|
||||
{
|
||||
None,
|
||||
Msaa2x,
|
||||
Msaa4x,
|
||||
Msaa8x
|
||||
}
|
||||
public struct RendererSettings
|
||||
{
|
||||
public Msaa Msaa;
|
||||
public bool UseVSync;
|
||||
public bool Fullscreen;
|
||||
public int TargetFps = 60;
|
||||
|
||||
public RendererSettings() { }
|
||||
|
||||
public static RendererSettings Default => new RendererSettings()
|
||||
{
|
||||
UseVSync = true,
|
||||
TargetFps = 60
|
||||
};
|
||||
}
|
||||
|
||||
public struct WindowSettings
|
||||
{
|
||||
public string Title;
|
||||
public Vector2 Size = new Vector2(640, 480);
|
||||
public bool Resizable { get; set; }
|
||||
|
||||
public WindowSettings(string title, Vector2 size)
|
||||
{
|
||||
Title = title;
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
Voile/Source/Rendering/Shader.cs
Normal file
8
Voile/Source/Rendering/Shader.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Voile.Rendering
|
||||
{
|
||||
public abstract class Shader
|
||||
{
|
||||
public abstract string FragmentSource { get; }
|
||||
public abstract string VertexSource { get; }
|
||||
}
|
||||
}
|
||||
194
Voile/Source/Rendering/StandardRenderer.cs
Normal file
194
Voile/Source/Rendering/StandardRenderer.cs
Normal file
@@ -0,0 +1,194 @@
|
||||
using System.Drawing;
|
||||
using System.Numerics;
|
||||
|
||||
using Silk.NET.GLFW;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace Voile.Rendering
|
||||
{
|
||||
/// <summary>
|
||||
/// A standard, OpenGL-based renderer.
|
||||
/// </summary>
|
||||
public class StandardRenderer : Renderer
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override Vector2 WindowSize { get; set; }
|
||||
/// <inheritdoc />
|
||||
public override bool ShouldRun => throw new NotImplementedException();
|
||||
|
||||
public override Vector2 MonitorSize => throw new NotImplementedException();
|
||||
|
||||
public override int TargetFps { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
||||
public override bool VSync { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
||||
public override string WindowTitle { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
||||
public override bool Fullscreen { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
||||
|
||||
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(RendererSettings settings)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void CreateAndInitialize(WindowSettings windowSettings, RendererSettings renderSettings)
|
||||
{
|
||||
CreateWindow(windowSettings);
|
||||
Initialize(renderSettings);
|
||||
}
|
||||
|
||||
public override void BeginFrame()
|
||||
{
|
||||
_glfw.PollEvents();
|
||||
_gl.Viewport(new Size((int)_windowSize.X, (int)_windowSize.Y));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void EndFrame()
|
||||
{
|
||||
// throw new NotImplementedException();
|
||||
EndFrameUnsafe();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ClearBackground(Color color)
|
||||
{
|
||||
_gl.ClearColor(color.ToSystemColor());
|
||||
_gl.Clear((uint)ClearBufferMask.ColorBufferBit);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Shutdown()
|
||||
{
|
||||
CloseWindowUnsafe();
|
||||
_glfw.Terminate();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void DrawCircle(float radius, Color color)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void DrawDebugText(string text, int fontSize, Color color)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void DrawRectangle(Vector2 size, Color color)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void DrawTexture(Texture2d texture, Color tint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override double GetFrameTime()
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void SetTargetFps(int fps)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetTransform(Matrix4x4 transform)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void SetWindowTitle(string title)
|
||||
{
|
||||
SetWindowTitleUnsafe(title);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void SetWindowVSync(bool value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool WindowShouldClose() => WindowShouldCloseUnsafe();
|
||||
|
||||
private unsafe void CreateWindowUnsafe(string title, Vector2 size)
|
||||
{
|
||||
_glfw = GlfwProvider.GLFW.Value;
|
||||
_glfw.Init();
|
||||
|
||||
_glfw.WindowHint(WindowHintInt.ContextVersionMajor, 4);
|
||||
_glfw.WindowHint(WindowHintInt.ContextVersionMinor, 6);
|
||||
_windowHandle = _glfw.CreateWindow((int)size.X, (int)size.Y, title, null, null);
|
||||
|
||||
_glfw.MakeContextCurrent(_windowHandle);
|
||||
_gl = GL.GetApi(_glfw.GetProcAddress);
|
||||
_glfw.SwapInterval(1);
|
||||
|
||||
_windowSize = size;
|
||||
}
|
||||
private unsafe void CloseWindowUnsafe() => _glfw.DestroyWindow(_windowHandle);
|
||||
private unsafe bool WindowShouldCloseUnsafe() => _glfw.WindowShouldClose(_windowHandle);
|
||||
private unsafe void SetWindowTitleUnsafe(string title) => _glfw.SetWindowTitle(_windowHandle, title);
|
||||
private unsafe void EndFrameUnsafe()
|
||||
{
|
||||
_glfw.SwapBuffers(_windowHandle);
|
||||
}
|
||||
|
||||
public override void DrawSdfText(string text, int fontSize, Color color)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void BeginCamera2d(Vector2 offset, Vector2 target, float rotation, float zoom)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void EndCamera2d()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void CreateWindow(WindowSettings windowSettings)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override int GetMonitorWidth(int monitorId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override int GetMonitorHeight(int monitorId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override int GetCurrentMonitor()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void DrawText(Font font, string text, Color color)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private GL _gl;
|
||||
private Glfw _glfw;
|
||||
private unsafe WindowHandle* _windowHandle;
|
||||
private Vector2 _windowSize;
|
||||
}
|
||||
}
|
||||
13
Voile/Source/Resources/Font.cs
Normal file
13
Voile/Source/Resources/Font.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Voile;
|
||||
|
||||
public class Font : Resource
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal handle for the font. If it got successfully loaded into the GPU, the value will be other than -1.
|
||||
/// </summary>
|
||||
internal int Handle { get; set; } = -1;
|
||||
public int Size { get; set; } = 16;
|
||||
public Font(string path, byte[] buffer) : base(path, buffer)
|
||||
{
|
||||
}
|
||||
}
|
||||
17
Voile/Source/Resources/Loaders/FontLoader.cs
Normal file
17
Voile/Source/Resources/Loaders/FontLoader.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace Voile.Resources;
|
||||
|
||||
public class FontLoader : IResourceLoader<Font>
|
||||
{
|
||||
public IEnumerable<string> SupportedExtensions => new string[]
|
||||
{
|
||||
"ttf"
|
||||
};
|
||||
|
||||
public Font Load(string path)
|
||||
{
|
||||
byte[] fileBuffer = File.ReadAllBytes(path);
|
||||
var result = new Font(path, fileBuffer);
|
||||
result.BufferSize = fileBuffer.Length;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
8
Voile/Source/Resources/Loaders/IResourceLoader.cs
Normal file
8
Voile/Source/Resources/Loaders/IResourceLoader.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Voile.Resources
|
||||
{
|
||||
public interface IResourceLoader<T> where T : Resource
|
||||
{
|
||||
public IEnumerable<string> SupportedExtensions { get; }
|
||||
public T Load(string path);
|
||||
}
|
||||
}
|
||||
52
Voile/Source/Resources/Loaders/SoundLoader.cs
Normal file
52
Voile/Source/Resources/Loaders/SoundLoader.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using StbVorbisSharp;
|
||||
|
||||
namespace Voile.Resources
|
||||
{
|
||||
public class SoundLoader : IResourceLoader<Sound>
|
||||
{
|
||||
public IEnumerable<string> SupportedExtensions => new string[]
|
||||
{
|
||||
"ogg"
|
||||
};
|
||||
|
||||
public Sound Load(string path)
|
||||
{
|
||||
Vorbis vorbis;
|
||||
Sound result;
|
||||
|
||||
var fileBuffer = File.ReadAllBytes(path);
|
||||
vorbis = Vorbis.FromMemory(fileBuffer);
|
||||
|
||||
vorbis.SubmitBuffer();
|
||||
|
||||
if (vorbis.Decoded == 0)
|
||||
{
|
||||
vorbis.Restart();
|
||||
vorbis.SubmitBuffer();
|
||||
}
|
||||
|
||||
var audioShort = vorbis.SongBuffer;
|
||||
int length = vorbis.Decoded * vorbis.Channels;
|
||||
byte[] audioData = new byte[length * 2];
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (i * 2 >= audioData.Length) break;
|
||||
|
||||
var b1 = (byte)(audioShort[i] >> 8);
|
||||
var b2 = (byte)(audioShort[i] & 256);
|
||||
|
||||
audioData[i * 2] = b2;
|
||||
audioData[i * 2 + 1] = b1;
|
||||
}
|
||||
|
||||
result = new Sound(path, audioData);
|
||||
result.Format = (SoundFormat)vorbis.Channels - 1;
|
||||
result.SampleRate = vorbis.SampleRate;
|
||||
result.BufferSize = length;
|
||||
|
||||
vorbis.Dispose();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Voile/Source/Resources/Loaders/Texture2dLoader.cs
Normal file
30
Voile/Source/Resources/Loaders/Texture2dLoader.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Voile.Resources;
|
||||
using StbImageSharp;
|
||||
|
||||
namespace Voile
|
||||
{
|
||||
public class Texture2dLoader : IResourceLoader<Texture2d>
|
||||
{
|
||||
public IEnumerable<string> SupportedExtensions => new string[]
|
||||
{
|
||||
".png",
|
||||
".jpg",
|
||||
".jpeg"
|
||||
};
|
||||
|
||||
public Texture2d Load(string path)
|
||||
{
|
||||
ImageResult image;
|
||||
using (var stream = File.OpenRead(path))
|
||||
{
|
||||
image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha);
|
||||
}
|
||||
|
||||
Texture2d result = new Texture2d(path, image.Data);
|
||||
result.Width = image.Width;
|
||||
result.Height = image.Height;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Voile/Source/Resources/Resource.cs
Normal file
30
Voile/Source/Resources/Resource.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Voile
|
||||
{
|
||||
public abstract class Resource : IDisposable
|
||||
{
|
||||
public string? Path { get => _path; set => _path = value; }
|
||||
|
||||
[JsonIgnore]
|
||||
public byte[]? Buffer { get => _buffer; set => _buffer = value; }
|
||||
|
||||
[JsonIgnore]
|
||||
public long BufferSize { get; set; }
|
||||
|
||||
public Resource(string path, byte[] buffer)
|
||||
{
|
||||
_path = path;
|
||||
_buffer = buffer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Buffer = null;
|
||||
Path = null;
|
||||
}
|
||||
|
||||
private string? _path;
|
||||
private byte[]? _buffer;
|
||||
}
|
||||
}
|
||||
171
Voile/Source/Resources/ResourceManager.cs
Normal file
171
Voile/Source/Resources/ResourceManager.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
using Voile.Utils;
|
||||
|
||||
namespace Voile.Resources
|
||||
{
|
||||
public class ResourceManager
|
||||
{
|
||||
public string ResourceRoot { get; set; } = "Resources/";
|
||||
|
||||
public bool TryLoad<T>(string resourceId, string path) where T : Resource
|
||||
{
|
||||
T? resource = null;
|
||||
|
||||
var fullPath = Path.Combine(ResourceRoot, path);
|
||||
|
||||
// TODO: don't check if file doesn't exist in the file system, make it more generic but for now it's fine
|
||||
if (!File.Exists(fullPath))
|
||||
{
|
||||
_logger.Error($"File at \"{path}\" doesn't exist!");
|
||||
return false;
|
||||
}
|
||||
|
||||
_logger.Info($"Loading {path} as {typeof(T)} with id \"{resourceId}\"...");
|
||||
|
||||
if (!TryGetLoader(out IResourceLoader<T>? loader))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var extension = Path.GetExtension(fullPath);
|
||||
var hasExtension = loader.SupportedExtensions.Any(ext => extension[1..] == ext);
|
||||
|
||||
if (!hasExtension)
|
||||
{
|
||||
_logger.Error($"Extension {extension} is not supported!");
|
||||
}
|
||||
|
||||
if (loader.Load(fullPath) is not T loadedResource)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
resource = loadedResource;
|
||||
_loadedResources.Add(resourceId, resource);
|
||||
|
||||
_logger.Info($"\"{resourceId}\" was loaded successfully.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TrySave<T>(string path, in T resource) where T : Resource
|
||||
{
|
||||
if (!TryGetSaver(out IResourceSaver<T>? saver))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!saver.TrySave(path, in resource))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetResource<T>(string resourceId, [NotNullWhen(true)] out T? resource) where T : Resource
|
||||
{
|
||||
resource = null;
|
||||
|
||||
if (!IsResourceLoaded(resourceId))
|
||||
{
|
||||
_logger.Error($"Resource \"{resourceId}\" has not yet been loaded!");
|
||||
return false;
|
||||
}
|
||||
|
||||
var expectedResource = _loadedResources[resourceId];
|
||||
|
||||
if (expectedResource is not T loadedResource)
|
||||
{
|
||||
_logger.Error($"Given resource is of wrong type: provided {typeof(T)}, expected {expectedResource.GetType()}!");
|
||||
return false;
|
||||
}
|
||||
|
||||
resource = loadedResource;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsResourceLoaded(string resourceId) => _loadedResources.ContainsKey(resourceId);
|
||||
|
||||
public void AddResourceLoaderAssociation<T>(IResourceLoader<T> loader) where T : Resource
|
||||
{
|
||||
_logger.Info($"Added resource loader association for {typeof(T)}.");
|
||||
_resourceLoaderAssociations.Add(typeof(T), loader);
|
||||
}
|
||||
|
||||
public void AddResourceSaverAssociation<T>(IResourceSaver<T> saver) where T : Resource
|
||||
{
|
||||
_logger.Info($"Added resource saver association for {typeof(T)}.");
|
||||
_resourceSaverAssociations.Add(typeof(T), saver);
|
||||
}
|
||||
|
||||
private bool TryGetLoader<T>([NotNullWhen(true)] out IResourceLoader<T>? loader) where T : Resource
|
||||
{
|
||||
loader = null;
|
||||
|
||||
if (!_resourceLoaderAssociations.ContainsKey(typeof(T)))
|
||||
{
|
||||
_logger.Error($"No loader association found for {typeof(T)}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
loader = _resourceLoaderAssociations[typeof(T)] as IResourceLoader<T>;
|
||||
|
||||
if (loader is not null)
|
||||
{
|
||||
_logger.Info($"Using {loader.GetType()} for loading...");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error($"No loader association found for {typeof(T)}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryGetSaver<T>([NotNullWhen(true)] out IResourceSaver<T>? saver) where T : Resource
|
||||
{
|
||||
saver = null;
|
||||
|
||||
if (!_resourceSaverAssociations.ContainsKey(typeof(T)))
|
||||
{
|
||||
_logger.Error($"No saver association found for {typeof(T)}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
saver = _resourceSaverAssociations[typeof(T)] as IResourceSaver<T>;
|
||||
|
||||
if (saver is not null)
|
||||
{
|
||||
_logger.Info($"Using {saver.GetType()} for saving...");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error($"No saver association found for {typeof(T)}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Logger _logger = new(nameof(ResourceManager));
|
||||
|
||||
private readonly Dictionary<Type, object> _resourceLoaderAssociations = new()
|
||||
{
|
||||
{typeof(Sound), new SoundLoader()},
|
||||
{typeof(Texture2d), new Texture2dLoader()},
|
||||
{typeof(Font), new FontLoader()}
|
||||
};
|
||||
|
||||
private readonly Dictionary<Type, object> _resourceSaverAssociations = new()
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
private Dictionary<string, Resource> _loadedResources = new();
|
||||
}
|
||||
}
|
||||
7
Voile/Source/Resources/Savers/IResourceSaver.cs
Normal file
7
Voile/Source/Resources/Savers/IResourceSaver.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Voile.Resources
|
||||
{
|
||||
public interface IResourceSaver<T> where T : Resource
|
||||
{
|
||||
public bool TrySave(string path, in T resource);
|
||||
}
|
||||
}
|
||||
18
Voile/Source/Resources/Sound.cs
Normal file
18
Voile/Source/Resources/Sound.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace Voile
|
||||
{
|
||||
public class Sound : Resource
|
||||
{
|
||||
public SoundFormat Format { get; set; }
|
||||
public int SampleRate { get; set; }
|
||||
|
||||
public Sound(string path, byte[] buffer) : base(path, buffer)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public enum SoundFormat
|
||||
{
|
||||
Mono,
|
||||
Stereo
|
||||
}
|
||||
}
|
||||
44
Voile/Source/Resources/Texture2d.cs
Normal file
44
Voile/Source/Resources/Texture2d.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
namespace Voile
|
||||
{
|
||||
public class Texture2d : Resource
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal handle for the texture. If it got successfully loaded into the GPU, the value will be other than -1.
|
||||
/// </summary>
|
||||
internal int Handle { get; set; } = -1;
|
||||
public int Width { get; set; }
|
||||
public int Height { get; set; }
|
||||
public int Mipmaps { get; set; } = 1;
|
||||
public TextureFormat Format { get; set; } = TextureFormat.UncompressedR8G8B8A8;
|
||||
public Texture2d(string path, byte[] buffer) : base(path, buffer)
|
||||
{
|
||||
}
|
||||
|
||||
public static Texture2d Empty => new Texture2d(string.Empty, new byte[] { });
|
||||
}
|
||||
|
||||
public enum TextureFormat
|
||||
{
|
||||
UncompressedGrayscale = 1,
|
||||
UncompressedGrayAlpha,
|
||||
UncompressedR5G6B5,
|
||||
UncompressedR8G8B8,
|
||||
UncompressedR5G5B5A1,
|
||||
UncompressedR4G4B4A4,
|
||||
UncompressedR8G8B8A8,
|
||||
UncompressedR32,
|
||||
UncompressedR32G32B32,
|
||||
UncompressedR32G32B32A32,
|
||||
CompressedDXT1Rgb,
|
||||
CompressedDXT1Rgba,
|
||||
CompressedDXT3Rgba,
|
||||
CompressedDXT5Rgba,
|
||||
CompressedETC1Rgb,
|
||||
CompressedETC2Rgb,
|
||||
CompressedETC2EACRgba,
|
||||
CompressedPVRTRgb,
|
||||
CompressedPVRTRgba,
|
||||
CompressedASTC4x4Rgba,
|
||||
CompressedASTC8x8Rgba
|
||||
}
|
||||
}
|
||||
25
Voile/Source/SceneGraph/Camera2d.cs
Normal file
25
Voile/Source/SceneGraph/Camera2d.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace Voile.SceneGraph;
|
||||
|
||||
public class Camera2d : Entity2d
|
||||
{
|
||||
public Vector2 Offset { get; set; }
|
||||
public float Zoom { get; set; } = 1f;
|
||||
public bool Current
|
||||
{
|
||||
get => _current; set
|
||||
{
|
||||
_current = value;
|
||||
Layer?.UpdateCurrentCamera();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnStart()
|
||||
{
|
||||
base.OnStart();
|
||||
Offset = Renderer.WindowSize / 2;
|
||||
}
|
||||
|
||||
private bool _current;
|
||||
}
|
||||
18
Voile/Source/SceneGraph/Entities/CircleShape2d.cs
Normal file
18
Voile/Source/SceneGraph/Entities/CircleShape2d.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Voile.Rendering;
|
||||
|
||||
namespace Voile.SceneGraph
|
||||
{
|
||||
public class CircleShape2d : Drawable2d
|
||||
{
|
||||
public float Radius { get => _radius; set => _radius = value; }
|
||||
public Color Color { get => _color; set => _color = value; }
|
||||
|
||||
public override void OnDraw(Renderer renderer)
|
||||
{
|
||||
renderer.DrawCircle(_radius, _color);
|
||||
}
|
||||
|
||||
private float _radius = 16;
|
||||
private Color _color = Color.White;
|
||||
}
|
||||
}
|
||||
17
Voile/Source/SceneGraph/Entities/Drawable2d.cs
Normal file
17
Voile/Source/SceneGraph/Entities/Drawable2d.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Numerics;
|
||||
using Voile.Rendering;
|
||||
|
||||
namespace Voile.SceneGraph
|
||||
{
|
||||
public abstract class Drawable2d : Entity2d, IDrawable
|
||||
{
|
||||
public Vector2 PivotOffset { get; set; }
|
||||
public void Draw(Renderer renderer)
|
||||
{
|
||||
renderer.SetTransform(Position, PivotOffset, Rotation);
|
||||
OnDraw(renderer);
|
||||
}
|
||||
|
||||
public abstract void OnDraw(Renderer renderer);
|
||||
}
|
||||
}
|
||||
37
Voile/Source/SceneGraph/Entities/Entity.cs
Normal file
37
Voile/Source/SceneGraph/Entities/Entity.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Voile.Audio;
|
||||
using Voile.Rendering;
|
||||
|
||||
namespace Voile.SceneGraph
|
||||
{
|
||||
public class Entity
|
||||
{
|
||||
[JsonIgnore] public EntityLayer? Layer { get; set; }
|
||||
[JsonIgnore] public InputHandler Input => Layer!.Scene.Input;
|
||||
[JsonIgnore] public AudioBackend Audio => Layer!.Scene.Audio;
|
||||
[JsonIgnore] public Renderer Renderer => Layer!.Scene.Renderer;
|
||||
public int Id { get; set; }
|
||||
|
||||
public void Start() => OnStart();
|
||||
public void Update(double dt) => OnUpdate(dt);
|
||||
public void ReceiveInput(InputHandler input) => OnInput(input);
|
||||
|
||||
protected virtual void OnStart() { }
|
||||
protected virtual void OnDestroy() { }
|
||||
protected virtual void OnUpdate(double dt) { }
|
||||
protected virtual void OnInput(InputHandler input) { }
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
OnDestroy();
|
||||
Layer?.DestroyEntity(Id);
|
||||
}
|
||||
}
|
||||
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSerializable(typeof(Entity))]
|
||||
internal partial class EntitySourceGenerationContext : JsonSerializerContext
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
10
Voile/Source/SceneGraph/Entities/Entity2d.cs
Normal file
10
Voile/Source/SceneGraph/Entities/Entity2d.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace Voile.SceneGraph
|
||||
{
|
||||
public class Entity2d : Entity
|
||||
{
|
||||
public Vector2 Position { get; set; }
|
||||
public float Rotation { get; set; }
|
||||
}
|
||||
}
|
||||
9
Voile/Source/SceneGraph/Entities/IDrawable.cs
Normal file
9
Voile/Source/SceneGraph/Entities/IDrawable.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Voile.Rendering;
|
||||
|
||||
namespace Voile.SceneGraph
|
||||
{
|
||||
public interface IDrawable
|
||||
{
|
||||
public void Draw(Renderer renderer);
|
||||
}
|
||||
}
|
||||
162
Voile/Source/SceneGraph/Entities/Particles2d.cs
Normal file
162
Voile/Source/SceneGraph/Entities/Particles2d.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
using System.Drawing;
|
||||
using System.Numerics;
|
||||
using Voile.Rendering;
|
||||
|
||||
namespace Voile.SceneGraph
|
||||
{
|
||||
// TODO: add oneshot parameter.
|
||||
public class Particles2d : Drawable2d
|
||||
{
|
||||
public int MaxParticles => _maxParticles;
|
||||
public ParticleSettings Settings => _settings;
|
||||
|
||||
public Particles2d(ParticleSettings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
_maxParticles = _settings.MaxParticles;
|
||||
_particleIndex = _maxParticles - 1;
|
||||
|
||||
_particles = new Particle[_maxParticles];
|
||||
}
|
||||
|
||||
public void Restart()
|
||||
{
|
||||
CleanupParticles();
|
||||
|
||||
// Allocate a new particle array if max particles property got changed.
|
||||
if (_maxParticles != _settings.MaxParticles)
|
||||
{
|
||||
_particles = new Particle[_maxParticles];
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnDraw(Renderer renderer)
|
||||
{
|
||||
foreach (var particle in _particles)
|
||||
{
|
||||
if (!particle.Alive) continue;
|
||||
|
||||
var t = particle.LifeTimeRemaining / particle.LifeTime;
|
||||
var scale = MathUtils.Lerp(_settings.ScaleEnd, _settings.ScaleBegin, t);
|
||||
var color = MathUtils.LerpColor(_settings.ColorEnd, _settings.ColorBegin, t);
|
||||
|
||||
renderer.SetTransform(particle.Position, Vector2.Zero, particle.Rotation);
|
||||
renderer.DrawRectangle(Vector2.One * scale, color);
|
||||
}
|
||||
}
|
||||
protected override void OnStart()
|
||||
{
|
||||
base.OnStart();
|
||||
// Emit();
|
||||
}
|
||||
protected override void OnUpdate(double dt)
|
||||
{
|
||||
base.OnUpdate(dt);
|
||||
|
||||
var rate = (int)MathUtils.Lerp(1, _maxParticles, _settings.Explosiveness);
|
||||
for (int i = 0; i < rate; i++)
|
||||
{
|
||||
Emit();
|
||||
}
|
||||
|
||||
for (int i = 0; i < _maxParticles; i++)
|
||||
{
|
||||
var particle = _particles[i];
|
||||
if (!particle.Alive) continue;
|
||||
|
||||
if (particle.LifeTimeRemaining <= 0.0f)
|
||||
{
|
||||
particle.Alive = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
particle.LifeTimeRemaining -= (float)dt;
|
||||
particle.Velocity += _settings.Gravity * (float)dt;
|
||||
particle.Position += particle.Velocity * (float)dt;
|
||||
particle.Rotation += particle.AngularVelocity * (float)dt;
|
||||
|
||||
particle.Velocity -= particle.Velocity * _settings.Damping * (float)dt;
|
||||
|
||||
_particles[i] = particle;
|
||||
}
|
||||
}
|
||||
|
||||
private void Emit()
|
||||
{
|
||||
Particle particle = _particles[_particleIndex];
|
||||
if (!(particle.LifeTimeRemaining <= 0)) return;
|
||||
particle.Alive = true;
|
||||
particle.Position = GetEmitPosition();
|
||||
particle.Velocity = _settings.Direction * _settings.LinearVelocity;
|
||||
|
||||
particle.Velocity += Vector2.One * _settings.LinearVelocityRandom * ((float)_random.NextDouble() - 0.5f);
|
||||
|
||||
particle.AngularVelocity = _settings.AngularVelocity;
|
||||
particle.AngularVelocity += 1f * _settings.AngularVelocityRandom * ((float)_random.NextDouble() - 0.5f);
|
||||
|
||||
particle.LifeTime = _settings.LifeTime;
|
||||
particle.LifeTimeRemaining = particle.LifeTime;
|
||||
|
||||
_particles[_particleIndex] = particle;
|
||||
_particleIndex = --_particleIndex <= 0 ? _maxParticles - 1 : --_particleIndex;
|
||||
}
|
||||
|
||||
private void CleanupParticles() => Array.Clear(_particles);
|
||||
private Vector2 GetEmitPosition()
|
||||
{
|
||||
// https://gamedev.stackexchange.com/questions/26713/calculate-random-points-pixel-within-a-circle-image
|
||||
var angle = _random.NextDouble() * Math.PI * 2;
|
||||
float radius = (float)Math.Sqrt(_random.NextDouble()) * _settings.EmitRadius;
|
||||
|
||||
float x = Position.X + radius * (float)Math.Cos(angle);
|
||||
float y = Position.Y + radius * (float)Math.Sin(angle);
|
||||
|
||||
return new Vector2(x, y);
|
||||
}
|
||||
|
||||
private ParticleSettings _settings;
|
||||
private int _maxParticles;
|
||||
private Particle[] _particles;
|
||||
private int _particleIndex;
|
||||
|
||||
// TODO: replace a random function for better distribution and performance.
|
||||
private LehmerRandom _random = new LehmerRandom();
|
||||
}
|
||||
|
||||
public struct ParticleSettings
|
||||
{
|
||||
public ParticleSettings()
|
||||
{
|
||||
}
|
||||
public float EmitRadius;
|
||||
public float LifeTime;
|
||||
public float Explosiveness;
|
||||
public int MaxParticles;
|
||||
public Vector2 Direction;
|
||||
public float LinearVelocity;
|
||||
public float AngularVelocity = 0.0f;
|
||||
public float AngularVelocityRandom;
|
||||
public float LinearVelocityRandom;
|
||||
public Vector2 Gravity;
|
||||
public float ScaleBegin = 16f;
|
||||
public float ScaleEnd = 0.0f;
|
||||
public Color ColorBegin = Color.White;
|
||||
public Color ColorEnd = Color.Black;
|
||||
public float Damping = 0.0f;
|
||||
}
|
||||
|
||||
public struct Particle
|
||||
{
|
||||
public Particle()
|
||||
{
|
||||
}
|
||||
public Vector2 Position;
|
||||
public Vector2 Velocity;
|
||||
public float AngularVelocity;
|
||||
public float LifeTime;
|
||||
public float LifeTimeRemaining;
|
||||
public float Scale;
|
||||
public float Rotation;
|
||||
public bool Alive = true;
|
||||
}
|
||||
}
|
||||
15
Voile/Source/SceneGraph/Entities/RectangleShape2d.cs
Normal file
15
Voile/Source/SceneGraph/Entities/RectangleShape2d.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Numerics;
|
||||
using Voile.Rendering;
|
||||
|
||||
namespace Voile.SceneGraph;
|
||||
|
||||
public class RectangleShape2d : Drawable2d
|
||||
{
|
||||
public Vector2 Size { get; set; } = Vector2.One * 32;
|
||||
public Color Color { get; set; } = Color.White;
|
||||
public override void OnDraw(Renderer renderer)
|
||||
{
|
||||
PivotOffset = Size / 2;
|
||||
renderer.DrawRectangle(Size, Color);
|
||||
}
|
||||
}
|
||||
23
Voile/Source/SceneGraph/Entities/Sprite2d.cs
Normal file
23
Voile/Source/SceneGraph/Entities/Sprite2d.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.Drawing;
|
||||
|
||||
using Voile.Rendering;
|
||||
|
||||
namespace Voile.SceneGraph
|
||||
{
|
||||
public class Sprite2d : Drawable2d
|
||||
{
|
||||
public Texture2d Texture { get => _texture ?? Texture2d.Empty; set => _texture = value; }
|
||||
|
||||
protected override void OnStart()
|
||||
{
|
||||
var renderer = Layer.Scene.Renderer;
|
||||
}
|
||||
|
||||
public override void OnDraw(Renderer renderer)
|
||||
{
|
||||
renderer.DrawTexture(_texture!, Color.White);
|
||||
}
|
||||
|
||||
private Texture2d? _texture;
|
||||
}
|
||||
}
|
||||
42
Voile/Source/SceneGraph/Entities/Text2d.cs
Normal file
42
Voile/Source/SceneGraph/Entities/Text2d.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Voile.Rendering;
|
||||
using System.Drawing;
|
||||
|
||||
namespace Voile.SceneGraph
|
||||
{
|
||||
public class Text2d : Drawable2d
|
||||
{
|
||||
public string Text { get => _text; set => _text = value; }
|
||||
public Color FontColor { get => _fontColor; set => _fontColor = value; }
|
||||
public Font Font
|
||||
{
|
||||
get => _font; set
|
||||
{
|
||||
_isDirty = true;
|
||||
_font = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Text2d(Font font)
|
||||
{
|
||||
_font = font;
|
||||
}
|
||||
|
||||
public override void OnDraw(Renderer renderer)
|
||||
{
|
||||
if (_font == null)
|
||||
{
|
||||
renderer.DrawDebugText(_text, 20, _fontColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderer.DrawText(_font, _text, _fontColor);
|
||||
}
|
||||
}
|
||||
|
||||
private string _text = string.Empty;
|
||||
private Color _fontColor = Color.White;
|
||||
private Font _font;
|
||||
private int _fontHandle;
|
||||
private bool _isDirty;
|
||||
}
|
||||
}
|
||||
116
Voile/Source/SceneGraph/EntityLayer.cs
Normal file
116
Voile/Source/SceneGraph/EntityLayer.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Voile.Rendering;
|
||||
using Voile.Utils;
|
||||
|
||||
namespace Voile.SceneGraph
|
||||
{
|
||||
public class EntityLayer : Layer
|
||||
{
|
||||
public List<Entity> Entities { get; set; }
|
||||
public Camera2d? CurrentCamera { get; set; }
|
||||
|
||||
public EntityLayer(List<Entity> entities)
|
||||
{
|
||||
Entities = entities;
|
||||
}
|
||||
|
||||
public EntityLayer()
|
||||
{
|
||||
Entities = new List<Entity>();
|
||||
}
|
||||
|
||||
public void UpdateCurrentCamera()
|
||||
{
|
||||
if (_cameraEntities.Count == 1)
|
||||
{
|
||||
CurrentCamera = _cameraEntities[0];
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var camera in _cameraEntities)
|
||||
{
|
||||
if (camera.Current) CurrentCamera = camera;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool AddEntity(Entity entity)
|
||||
{
|
||||
entity.Id = Entities.Count;
|
||||
entity.Layer = this;
|
||||
|
||||
if (entity is Camera2d camera2d)
|
||||
{
|
||||
_cameraEntities.Add(camera2d);
|
||||
UpdateCurrentCamera();
|
||||
}
|
||||
|
||||
Entities.Add(entity);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void DestroyEntity(int at)
|
||||
{
|
||||
Entities.RemoveAt(at);
|
||||
}
|
||||
|
||||
protected override void OnStart()
|
||||
{
|
||||
for (int i = 0; i < Entities.Count; i++)
|
||||
{
|
||||
var entity = Entities[i];
|
||||
entity.Layer = this;
|
||||
entity.Start();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnUpdate(double dt)
|
||||
{
|
||||
foreach (var entity in Entities)
|
||||
{
|
||||
entity.Update(dt);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnBeginDraw(Renderer renderer)
|
||||
{
|
||||
if (CurrentCamera is not null)
|
||||
{
|
||||
renderer.BeginCamera2d(CurrentCamera.Offset, CurrentCamera.Position, 0f, CurrentCamera.Zoom);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnEndDraw(Renderer renderer)
|
||||
{
|
||||
if (CurrentCamera is not null)
|
||||
{
|
||||
renderer.EndCamera2d();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void OnDraw(Renderer renderer)
|
||||
{
|
||||
// TODO: can be done more efficiently, needs rendering redesign.
|
||||
foreach (var entity in Entities)
|
||||
{
|
||||
if (entity is IDrawable drawable)
|
||||
{
|
||||
drawable.Draw(renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
private List<Camera2d> _cameraEntities = new();
|
||||
}
|
||||
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSerializable(typeof(EntityLayer))]
|
||||
[JsonSerializable(typeof(Entity))]
|
||||
[JsonSerializable(typeof(List<Entity>))]
|
||||
internal partial class EntityLayerContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
}
|
||||
11
Voile/Source/SceneGraph/IMainLoop.cs
Normal file
11
Voile/Source/SceneGraph/IMainLoop.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Voile.SceneGraph
|
||||
{
|
||||
public interface IMainLoop
|
||||
{
|
||||
void Init();
|
||||
void Start();
|
||||
void Update();
|
||||
double DeltaTime { get; }
|
||||
bool ShouldRun { get; }
|
||||
}
|
||||
}
|
||||
28
Voile/Source/SceneGraph/Layer.cs
Normal file
28
Voile/Source/SceneGraph/Layer.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Voile.Resources;
|
||||
using Voile.Rendering;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Voile.SceneGraph
|
||||
{
|
||||
public abstract class Layer : IDrawable
|
||||
{
|
||||
[JsonIgnore] public Scene? Scene { get; set; }
|
||||
[JsonIgnore] public InputHandler? Input { get; set; }
|
||||
[JsonIgnore] public ResourceManager ResourceManager => Scene!.ResourceManager;
|
||||
|
||||
public void BeginDraw(Renderer renderer) => OnBeginDraw(renderer);
|
||||
public void Draw(Renderer renderer) => OnDraw(renderer);
|
||||
public void EndDraw(Renderer renderer) => OnEndDraw(renderer);
|
||||
|
||||
public void Start() => OnStart();
|
||||
public void Update(double dt) => OnUpdate(dt);
|
||||
public void ReceiveInput(InputHandler input) => OnInput(input);
|
||||
|
||||
protected virtual void OnStart() { }
|
||||
protected virtual void OnUpdate(double dt) { }
|
||||
protected virtual void OnInput(InputHandler input) { }
|
||||
protected abstract void OnBeginDraw(Renderer renderer);
|
||||
protected abstract void OnDraw(Renderer renderer);
|
||||
protected abstract void OnEndDraw(Renderer renderer);
|
||||
}
|
||||
}
|
||||
24
Voile/Source/SceneGraph/Resources/SerializedScene.cs
Normal file
24
Voile/Source/SceneGraph/Resources/SerializedScene.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Voile.SceneGraph
|
||||
{
|
||||
public class SerializedScene : Resource
|
||||
{
|
||||
public Dictionary<string, Layer> Layers { get; set; }
|
||||
|
||||
public SerializedScene(string path, byte[] buffer) : base(path, buffer)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSerializable(typeof(SerializedScene))]
|
||||
[JsonSerializable(typeof(Dictionary<string, Layer>))]
|
||||
[JsonSerializable(typeof(Entity2d))]
|
||||
[JsonSerializable(typeof(Layer))]
|
||||
[JsonSerializable(typeof(EntityLayer))]
|
||||
internal partial class SerializedSceneContext : JsonSerializerContext
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
23
Voile/Source/SceneGraph/Resources/SerializedSceneSaver.cs
Normal file
23
Voile/Source/SceneGraph/Resources/SerializedSceneSaver.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Voile.Resources;
|
||||
using Voile.Utils;
|
||||
|
||||
namespace Voile.SceneGraph
|
||||
{
|
||||
public class SerializedSceneSaver : IResourceSaver<SerializedScene>
|
||||
{
|
||||
public bool TrySave(string path, in SerializedScene resource)
|
||||
{
|
||||
if (resource.Buffer is null)
|
||||
{
|
||||
_logger.Error($"Tried to save a resource at \"{path}\" with a null buffer!");
|
||||
return false;
|
||||
}
|
||||
|
||||
File.WriteAllBytes(path, resource.Buffer);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Logger _logger = new(nameof(SerializedSceneSaver));
|
||||
}
|
||||
}
|
||||
130
Voile/Source/SceneGraph/Scene.cs
Normal file
130
Voile/Source/SceneGraph/Scene.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System.Text.Json;
|
||||
using Voile.Audio;
|
||||
using Voile.Rendering;
|
||||
using Voile.Resources;
|
||||
|
||||
|
||||
namespace Voile.SceneGraph
|
||||
{
|
||||
public class Scene : IMainLoop
|
||||
{
|
||||
public Renderer Renderer { get => _renderer; set => _renderer = value; }
|
||||
public InputHandler? Input { get => _input; set => _input = value; }
|
||||
public AudioBackend? Audio => _audioBackend;
|
||||
public ResourceManager ResourceManager => _resourceManager;
|
||||
|
||||
public double DeltaTime => _renderer.FrameTime;
|
||||
public bool ShouldRun => Renderer.ShouldRun;
|
||||
|
||||
public Scene(SceneSettings settings)
|
||||
{
|
||||
_renderer = settings.Renderer;
|
||||
_input = settings.InputHandler;
|
||||
_audioBackend = settings.AudioBackend;
|
||||
_resourceManager = settings.ResourceManager;
|
||||
}
|
||||
|
||||
public static Scene FromSerialized(SerializedScene serializedScene, SceneSettings settings)
|
||||
{
|
||||
var scene = new Scene(settings);
|
||||
scene.WithLayers(serializedScene.Layers);
|
||||
|
||||
return scene;
|
||||
}
|
||||
|
||||
public bool TrySerialize(out SerializedScene serializedScene)
|
||||
{
|
||||
serializedScene = new SerializedScene(string.Empty, new byte[] { })
|
||||
{
|
||||
Layers = _layers
|
||||
};
|
||||
|
||||
|
||||
serializedScene.Buffer = JsonSerializer.SerializeToUtf8Bytes(serializedScene, new JsonSerializerOptions
|
||||
{
|
||||
TypeInfoResolver = SerializedSceneContext.Default
|
||||
});
|
||||
serializedScene.BufferSize = serializedScene.Buffer.LongLength;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void WithLayers(Dictionary<string, Layer> layers)
|
||||
{
|
||||
_layers = layers;
|
||||
}
|
||||
|
||||
public void Init() => SetupRenderer();
|
||||
public void Start()
|
||||
{
|
||||
foreach (var layer in _layers.Values)
|
||||
{
|
||||
if (_input is not null)
|
||||
{
|
||||
layer.Input = _input;
|
||||
}
|
||||
|
||||
layer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
foreach (var layer in _layers)
|
||||
{
|
||||
layer.Value.Update(DeltaTime);
|
||||
}
|
||||
|
||||
Audio?.Update();
|
||||
}
|
||||
|
||||
public void AddLayer(string name, Layer layer)
|
||||
{
|
||||
layer.Scene = this;
|
||||
_layers.Add(name, layer);
|
||||
}
|
||||
|
||||
public void BeginDraw()
|
||||
{
|
||||
Renderer.BeginFrame();
|
||||
Renderer.ClearBackground(Color.Black);
|
||||
|
||||
foreach (var layer in _layers.Values)
|
||||
{
|
||||
layer.BeginDraw(_renderer);
|
||||
layer.Draw(_renderer);
|
||||
}
|
||||
|
||||
Renderer.ResetTransform();
|
||||
}
|
||||
|
||||
public void EndDraw()
|
||||
{
|
||||
foreach (var layer in _layers.Values)
|
||||
{
|
||||
layer.EndDraw(_renderer);
|
||||
}
|
||||
|
||||
Renderer.EndFrame();
|
||||
}
|
||||
|
||||
private void SetupRenderer()
|
||||
{
|
||||
Renderer.Initialize(new RendererSettings { Msaa = Msaa.Msaa4x, UseVSync = true });
|
||||
}
|
||||
|
||||
private Dictionary<string, Layer> _layers = new();
|
||||
private Renderer _renderer;
|
||||
private AudioBackend? _audioBackend;
|
||||
private InputHandler? _input;
|
||||
private ResourceManager _resourceManager;
|
||||
}
|
||||
|
||||
public struct SceneSettings
|
||||
{
|
||||
public Renderer Renderer { get; set; }
|
||||
public AudioBackend AudioBackend { get; set; }
|
||||
public InputHandler InputHandler { get; set; }
|
||||
public ResourceManager ResourceManager { get; set; }
|
||||
}
|
||||
}
|
||||
63
Voile/Source/UI/Container.cs
Normal file
63
Voile/Source/UI/Container.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.Numerics;
|
||||
using Voile.Rendering;
|
||||
|
||||
namespace Voile.UI;
|
||||
|
||||
/// <summary>
|
||||
/// A basic container for UI elements. All container's children will update their constraints based on container's sizing and positioning.
|
||||
/// </summary>
|
||||
public class Container : UIElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Updates the sizes of the container and rearranges its children.
|
||||
/// </summary>
|
||||
/// <param name="position"></param>
|
||||
/// <param name="size"></param>
|
||||
public void UpdateRect(Vector2 position, Vector2 size)
|
||||
{
|
||||
Rect.Position = position;
|
||||
|
||||
UpdateSize(size);
|
||||
RearrangeChildren();
|
||||
}
|
||||
|
||||
protected void RearrangeChildren()
|
||||
{
|
||||
int idx = 0;
|
||||
foreach (var child in children)
|
||||
{
|
||||
if (child is Container container)
|
||||
{
|
||||
container.UpdateRect(Rect.Position, Rect.Size);
|
||||
}
|
||||
RearrangeChild(idx, child);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void RearrangeChild(int idx, UIElement child) { }
|
||||
|
||||
protected override void OnRender(Renderer renderer)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void UpdateSize(Vector2 baseSize)
|
||||
{
|
||||
if (parent == null)
|
||||
{
|
||||
Rect.Size = baseSize;
|
||||
return;
|
||||
}
|
||||
|
||||
if (VerticalSizeFlags == SizeFlags.Fill)
|
||||
{
|
||||
Rect.Size = new Vector2(Rect.Size.X, baseSize.Y * ExpandRatio.Y);
|
||||
}
|
||||
|
||||
if (HorizontalSizeFlags == SizeFlags.Fill)
|
||||
{
|
||||
Rect.Size = new Vector2(baseSize.X * ExpandRatio.X, Rect.Size.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
24
Voile/Source/UI/MarginPanel.cs
Normal file
24
Voile/Source/UI/MarginPanel.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace Voile.UI;
|
||||
|
||||
public class MarginPanel : Panel
|
||||
{
|
||||
public Vector2 RelativeMargin { get; set; }
|
||||
public Vector2 AbsoluteMargin { get; set; }
|
||||
public MarginPanel(PanelStyle style) : base(style)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void RearrangeChild(int idx, UIElement child)
|
||||
{
|
||||
base.RearrangeChild(idx, child);
|
||||
|
||||
var rect = child.Rect;
|
||||
|
||||
var absoluteMargin = Rect.Size * RelativeMargin + AbsoluteMargin;
|
||||
|
||||
rect.Position = Rect.Position + absoluteMargin;
|
||||
rect.Size = rect.Size - absoluteMargin * 2;
|
||||
}
|
||||
}
|
||||
20
Voile/Source/UI/Panel.cs
Normal file
20
Voile/Source/UI/Panel.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Numerics;
|
||||
using Voile.Rendering;
|
||||
|
||||
namespace Voile.UI;
|
||||
|
||||
public class Panel : Container
|
||||
{
|
||||
public PanelStyle Style { get; set; }
|
||||
|
||||
public Panel(PanelStyle style)
|
||||
{
|
||||
Style = style;
|
||||
}
|
||||
|
||||
protected override void OnRender(Renderer renderer)
|
||||
{
|
||||
base.OnRender(renderer);
|
||||
renderer.DrawRectangle(Rect.Size, Style.BackgroundColor);
|
||||
}
|
||||
}
|
||||
6
Voile/Source/UI/PanelStyle.cs
Normal file
6
Voile/Source/UI/PanelStyle.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Voile.UI;
|
||||
|
||||
public struct PanelStyle
|
||||
{
|
||||
public Color BackgroundColor { get; set; }
|
||||
}
|
||||
10
Voile/Source/UI/Rect.cs
Normal file
10
Voile/Source/UI/Rect.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace Voile.UI;
|
||||
|
||||
public class Rect
|
||||
{
|
||||
public Vector2 Position { get; set; }
|
||||
public Vector2 Size { get; set; }
|
||||
public Vector2 Scale { get; set; }
|
||||
}
|
||||
31
Voile/Source/UI/TextLabel.cs
Normal file
31
Voile/Source/UI/TextLabel.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Voile.Rendering;
|
||||
|
||||
namespace Voile.UI;
|
||||
|
||||
public class TextLabel : UIElement
|
||||
{
|
||||
public string Text { get; set; } = string.Empty;
|
||||
public Font Font
|
||||
{
|
||||
get => _font; set
|
||||
{
|
||||
_font = value;
|
||||
}
|
||||
}
|
||||
public int FontSize { get; set; } = 16;
|
||||
public Color FontColor { get; set; } = Color.White;
|
||||
protected override void OnRender(Renderer renderer)
|
||||
{
|
||||
|
||||
if (_font == null)
|
||||
{
|
||||
renderer.DrawDebugText(Text, FontSize, FontColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderer.DrawText(_font, Text, FontColor);
|
||||
}
|
||||
}
|
||||
|
||||
private Font? _font;
|
||||
}
|
||||
55
Voile/Source/UI/UIElement.cs
Normal file
55
Voile/Source/UI/UIElement.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System.Numerics;
|
||||
using Voile.Rendering;
|
||||
using Voile.Utils;
|
||||
|
||||
namespace Voile.UI;
|
||||
|
||||
public abstract class UIElement
|
||||
{
|
||||
public Rect Rect { get; set; } = new Rect();
|
||||
public SizeFlags VerticalSizeFlags { get; set; } = SizeFlags.Fill;
|
||||
public SizeFlags HorizontalSizeFlags { get; set; } = SizeFlags.Fill;
|
||||
|
||||
public Vector2 ExpandRatio { get; set; } = Vector2.One;
|
||||
|
||||
public UIElement()
|
||||
{
|
||||
children = new();
|
||||
}
|
||||
|
||||
public void AddChild(UIElement child)
|
||||
{
|
||||
children.Add(child);
|
||||
child.parent = this;
|
||||
}
|
||||
|
||||
public void Render(Renderer renderer)
|
||||
{
|
||||
Vector2 parentPos = parent != null ? parent.Rect.Position : Vector2.Zero;
|
||||
renderer.SetTransform(Rect.Position + parentPos, Vector2.Zero, 0);
|
||||
OnRender(renderer);
|
||||
|
||||
foreach (UIElement child in children)
|
||||
{
|
||||
renderer.SetTransform(child.Rect.Position, Vector2.Zero, 0);
|
||||
child.Render(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void OnRender(Renderer renderer);
|
||||
|
||||
protected List<UIElement> children;
|
||||
protected UIElement? parent;
|
||||
|
||||
private Logger _logger = new(nameof(UIElement));
|
||||
}
|
||||
|
||||
|
||||
[Flags]
|
||||
public enum SizeFlags
|
||||
{
|
||||
ShrinkBegin,
|
||||
ShrinkCenter,
|
||||
ShrinkEnd,
|
||||
Fill
|
||||
}
|
||||
22
Voile/Source/UI/VerticalPanel.cs
Normal file
22
Voile/Source/UI/VerticalPanel.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace Voile.UI;
|
||||
|
||||
public class VerticalPanel : Panel
|
||||
{
|
||||
public float Spacing { get; set; } = 16;
|
||||
public VerticalPanel(PanelStyle style) : base(style)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void RearrangeChild(int idx, UIElement child)
|
||||
{
|
||||
base.RearrangeChild(idx, child);
|
||||
|
||||
var yOffset = idx * Spacing;
|
||||
var rect = child.Rect;
|
||||
|
||||
rect.Position = Rect.Position;
|
||||
rect.Position = new Vector2(rect.Position.X, yOffset);
|
||||
}
|
||||
}
|
||||
330
Voile/Source/Utils/ImGuiRenderLayer.cs
Normal file
330
Voile/Source/Utils/ImGuiRenderLayer.cs
Normal file
@@ -0,0 +1,330 @@
|
||||
using System.Numerics;
|
||||
using ImGuiNET;
|
||||
using Raylib_cs;
|
||||
|
||||
using Voile.Rendering;
|
||||
|
||||
namespace Voile.SceneGraph
|
||||
{
|
||||
public class ImGuiRenderLayer : Layer
|
||||
{
|
||||
protected override void OnDraw(Renderer renderer)
|
||||
{
|
||||
Layout();
|
||||
_controller.Draw(renderer);
|
||||
}
|
||||
|
||||
protected virtual void Layout() { }
|
||||
|
||||
protected override void OnStart()
|
||||
{
|
||||
_controller = new ImGuiController();
|
||||
_controller.Load(Scene.Renderer.WindowSize);
|
||||
}
|
||||
|
||||
protected override void OnUpdate(double dt)
|
||||
{
|
||||
_controller.Update(dt, Input);
|
||||
}
|
||||
|
||||
protected override void OnBeginDraw(Renderer renderer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override void OnEndDraw(Renderer renderer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private ImGuiController _controller;
|
||||
}
|
||||
|
||||
public class ImGuiController : IDisposable, IDrawable
|
||||
{
|
||||
public ImGuiController()
|
||||
{
|
||||
_context = ImGui.CreateContext();
|
||||
ImGui.SetCurrentContext(_context);
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
ImGui.DestroyContext();
|
||||
}
|
||||
|
||||
public void Load(Vector2 size)
|
||||
{
|
||||
ImGuiIOPtr io = ImGui.GetIO();
|
||||
io.Fonts.AddFontDefault();
|
||||
|
||||
Resize(size);
|
||||
LoadFontTexture();
|
||||
SetupInput();
|
||||
ImGui.NewFrame();
|
||||
}
|
||||
|
||||
unsafe void LoadFontTexture()
|
||||
{
|
||||
ImGuiIOPtr io = ImGui.GetIO();
|
||||
io.Fonts.GetTexDataAsRGBA32(out byte* pixels, out int width, out int height);
|
||||
|
||||
// TODO: use engine API instead of Raylib.
|
||||
Image image = new Image
|
||||
{
|
||||
data = pixels,
|
||||
width = width,
|
||||
height = height,
|
||||
mipmaps = 1,
|
||||
format = PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8,
|
||||
};
|
||||
_fontTexture = Raylib.LoadTextureFromImage(image);
|
||||
|
||||
io.Fonts.SetTexID(new IntPtr(_fontTexture.id));
|
||||
io.Fonts.ClearTexData();
|
||||
}
|
||||
|
||||
private void SetupInput()
|
||||
{
|
||||
ImGuiIOPtr io = ImGui.GetIO();
|
||||
|
||||
// Setup config flags
|
||||
io.ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard;
|
||||
|
||||
// Setup back-end capabilities flags
|
||||
io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors;
|
||||
io.BackendFlags |= ImGuiBackendFlags.HasSetMousePos;
|
||||
|
||||
// Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array.
|
||||
io.KeyMap[(int)ImGuiKey.Tab] = (int)KeyboardKey.Tab;
|
||||
io.KeyMap[(int)ImGuiKey.LeftArrow] = (int)KeyboardKey.Left;
|
||||
io.KeyMap[(int)ImGuiKey.RightArrow] = (int)KeyboardKey.Right;
|
||||
io.KeyMap[(int)ImGuiKey.UpArrow] = (int)KeyboardKey.Up;
|
||||
io.KeyMap[(int)ImGuiKey.DownArrow] = (int)KeyboardKey.Down;
|
||||
io.KeyMap[(int)ImGuiKey.PageUp] = (int)KeyboardKey.PageUp;
|
||||
io.KeyMap[(int)ImGuiKey.PageDown] = (int)KeyboardKey.PageDown;
|
||||
io.KeyMap[(int)ImGuiKey.Home] = (int)KeyboardKey.Home;
|
||||
io.KeyMap[(int)ImGuiKey.End] = (int)KeyboardKey.End;
|
||||
io.KeyMap[(int)ImGuiKey.Insert] = (int)KeyboardKey.Insert;
|
||||
io.KeyMap[(int)ImGuiKey.Delete] = (int)KeyboardKey.Delete;
|
||||
io.KeyMap[(int)ImGuiKey.Backspace] = (int)KeyboardKey.Backspace;
|
||||
io.KeyMap[(int)ImGuiKey.Space] = (int)KeyboardKey.Spacebar;
|
||||
io.KeyMap[(int)ImGuiKey.Enter] = (int)KeyboardKey.Enter;
|
||||
io.KeyMap[(int)ImGuiKey.Escape] = (int)KeyboardKey.Escape;
|
||||
io.KeyMap[(int)ImGuiKey.A] = (int)KeyboardKey.A;
|
||||
io.KeyMap[(int)ImGuiKey.C] = (int)KeyboardKey.C;
|
||||
io.KeyMap[(int)ImGuiKey.V] = (int)KeyboardKey.V;
|
||||
io.KeyMap[(int)ImGuiKey.X] = (int)KeyboardKey.X;
|
||||
io.KeyMap[(int)ImGuiKey.Y] = (int)KeyboardKey.Y;
|
||||
io.KeyMap[(int)ImGuiKey.Z] = (int)KeyboardKey.Z;
|
||||
}
|
||||
|
||||
public void Resize(Vector2 size)
|
||||
{
|
||||
ImGuiIOPtr io = ImGui.GetIO();
|
||||
io.DisplaySize = size / _scaleFactor;
|
||||
}
|
||||
|
||||
public void Update(double dt, InputHandler input)
|
||||
{
|
||||
ImGuiIOPtr io = ImGui.GetIO();
|
||||
|
||||
io.DisplayFramebufferScale = Vector2.One;
|
||||
io.DeltaTime = (float)dt;
|
||||
|
||||
UpdateKeyboard(input);
|
||||
UpdateMouse(input);
|
||||
|
||||
ImGui.NewFrame();
|
||||
}
|
||||
|
||||
private void UpdateKeyboard(InputHandler input)
|
||||
{
|
||||
ImGuiIOPtr io = ImGui.GetIO();
|
||||
|
||||
// Modifiers are not reliable across systems
|
||||
io.KeyCtrl = io.KeysDown[(int)KeyboardKey.LeftControl] || io.KeysDown[(int)KeyboardKey.RightControl];
|
||||
io.KeyShift = io.KeysDown[(int)KeyboardKey.LeftShift] || io.KeysDown[(int)KeyboardKey.RightShift];
|
||||
io.KeyAlt = io.KeysDown[(int)KeyboardKey.LeftAlt] || io.KeysDown[(int)KeyboardKey.RightAlt];
|
||||
io.KeySuper = io.KeysDown[(int)KeyboardKey.LeftSuper] || io.KeysDown[(int)KeyboardKey.RightSuper];
|
||||
|
||||
// Key states
|
||||
for (int i = (int)KeyboardKey.Spacebar; i < (int)KeyboardKey.KBMenu + 1; i++)
|
||||
{
|
||||
io.KeysDown[i] = input.IsKeyboardKeyDown((KeyboardKey)i);
|
||||
}
|
||||
|
||||
// Key input
|
||||
int keyPressed = input.GetCharPressed();
|
||||
if (keyPressed != 0)
|
||||
{
|
||||
io.AddInputCharacter((uint)keyPressed);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateMouse(InputHandler input)
|
||||
{
|
||||
ImGuiIOPtr io = ImGui.GetIO();
|
||||
|
||||
// Store button states
|
||||
for (int i = 0; i < io.MouseDown.Count; i++)
|
||||
{
|
||||
io.MouseDown[i] = input.IsMouseButtonDown((MouseButton)i);
|
||||
}
|
||||
|
||||
// Mouse scroll
|
||||
io.MouseWheel += input.GetMouseWheelMovement();
|
||||
|
||||
// Mouse position
|
||||
Vector2 mousePosition = io.MousePos;
|
||||
// TODO:
|
||||
// bool focused = Raylib.IsWindowFocused();
|
||||
|
||||
if (io.WantSetMousePos)
|
||||
{
|
||||
input.SetMousePosition(mousePosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
io.MousePos = input.GetMousePosition();
|
||||
}
|
||||
|
||||
// Mouse cursor state
|
||||
if ((io.ConfigFlags & ImGuiConfigFlags.NoMouseCursorChange) == 0 || input.IsCursorHidden())
|
||||
{
|
||||
ImGuiMouseCursor cursor = ImGui.GetMouseCursor();
|
||||
if (cursor == ImGuiMouseCursor.None || io.MouseDrawCursor)
|
||||
{
|
||||
input.HideCursor();
|
||||
}
|
||||
else
|
||||
{
|
||||
input.ShowCursor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Render()
|
||||
{
|
||||
ImGui.Render();
|
||||
}
|
||||
|
||||
private void RenderCommandLists(ImDrawDataPtr data)
|
||||
{
|
||||
// Scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
|
||||
int fbWidth = (int)(data.DisplaySize.X * data.FramebufferScale.X);
|
||||
int fbHeight = (int)(data.DisplaySize.Y * data.FramebufferScale.Y);
|
||||
|
||||
// Avoid rendering if display is minimized or if the command list is empty
|
||||
if (fbWidth <= 0 || fbHeight <= 0 || data.CmdListsCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Rlgl.rlDrawRenderBatchActive();
|
||||
Rlgl.rlDisableBackfaceCulling();
|
||||
Rlgl.rlEnableScissorTest();
|
||||
|
||||
data.ScaleClipRects(ImGui.GetIO().DisplayFramebufferScale);
|
||||
|
||||
for (int n = 0; n < data.CmdListsCount; n++)
|
||||
{
|
||||
int idxOffset = 0;
|
||||
ImDrawListPtr cmdList = data.CmdListsRange[n];
|
||||
|
||||
// Vertex buffer and index buffer generated by DearImGui
|
||||
ImPtrVector<ImDrawVertPtr> vtxBuffer = cmdList.VtxBuffer;
|
||||
ImVector<ushort> idxBuffer = cmdList.IdxBuffer;
|
||||
|
||||
for (int cmdi = 0; cmdi < cmdList.CmdBuffer.Size; cmdi++)
|
||||
{
|
||||
ImDrawCmdPtr pcmd = cmdList.CmdBuffer[cmdi];
|
||||
|
||||
// Scissor rect
|
||||
Vector2 pos = data.DisplayPos;
|
||||
int rectX = (int)((pcmd.ClipRect.X - pos.X) * data.FramebufferScale.X);
|
||||
int rectY = (int)((pcmd.ClipRect.Y - pos.Y) * data.FramebufferScale.Y);
|
||||
int rectW = (int)((pcmd.ClipRect.Z - rectX) * data.FramebufferScale.Y);
|
||||
int rectH = (int)((pcmd.ClipRect.W - rectY) * data.FramebufferScale.Y);
|
||||
Rlgl.rlScissor(rectX, Raylib.GetScreenHeight() - (rectY + rectH), rectW, rectH);
|
||||
|
||||
if (pcmd.UserCallback != IntPtr.Zero)
|
||||
{
|
||||
// pcmd.UserCallback(cmdList, pcmd);
|
||||
idxOffset += (int)pcmd.ElemCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawTriangles(pcmd.ElemCount, idxOffset, (int)pcmd.VtxOffset, idxBuffer, vtxBuffer, pcmd.TextureId);
|
||||
idxOffset += (int)pcmd.ElemCount;
|
||||
Rlgl.rlDrawRenderBatchActive();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rlgl.rlSetTexture(0);
|
||||
Rlgl.rlDisableScissorTest();
|
||||
Rlgl.rlEnableBackfaceCulling();
|
||||
}
|
||||
|
||||
private Color GetColor(uint hexValue)
|
||||
{
|
||||
Color color = new Color();
|
||||
|
||||
color.R = (byte)(hexValue & 0xFF) / 255f;
|
||||
color.G = (byte)((hexValue >> 8) & 0xFF) / 255f;
|
||||
color.B = (byte)((hexValue >> 16) & 0xFF) / 255f;
|
||||
color.A = (byte)((hexValue >> 24) & 0xFF) / 255f;
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
void DrawTriangleVertex(ImDrawVertPtr idxVert)
|
||||
{
|
||||
Color c = GetColor(idxVert.col);
|
||||
Rlgl.rlColor4ub((byte)Math.Round(c.R * 255f), (byte)Math.Round(c.G * 255f), (byte)Math.Round(c.B * 255f), (byte)Math.Round(c.A * 255f));
|
||||
Rlgl.rlTexCoord2f(idxVert.uv.X, idxVert.uv.Y);
|
||||
Rlgl.rlVertex2f(idxVert.pos.X, idxVert.pos.Y);
|
||||
}
|
||||
|
||||
// Draw the imgui triangle data
|
||||
private void DrawTriangles(uint count, int idxOffset, int vtxOffset, ImVector<ushort> idxBuffer, ImPtrVector<ImDrawVertPtr> idxVert, IntPtr textureId)
|
||||
{
|
||||
ushort index = 0;
|
||||
ImDrawVertPtr vertex;
|
||||
|
||||
if (Rlgl.rlCheckRenderBatchLimit((int)count * 3))
|
||||
{
|
||||
Rlgl.rlDrawRenderBatchActive();
|
||||
}
|
||||
|
||||
Rlgl.rlBegin(DrawMode.TRIANGLES);
|
||||
Rlgl.rlSetTexture((uint)textureId);
|
||||
|
||||
for (int i = 0; i <= (count - 3); i += 3)
|
||||
{
|
||||
index = idxBuffer[idxOffset + i];
|
||||
vertex = idxVert[vtxOffset + index];
|
||||
DrawTriangleVertex(vertex);
|
||||
|
||||
index = idxBuffer[idxOffset + i + 1];
|
||||
vertex = idxVert[vtxOffset + index];
|
||||
DrawTriangleVertex(vertex);
|
||||
|
||||
index = idxBuffer[idxOffset + i + 2];
|
||||
vertex = idxVert[vtxOffset + index];
|
||||
DrawTriangleVertex(vertex);
|
||||
}
|
||||
Rlgl.rlEnd();
|
||||
}
|
||||
|
||||
public void Draw(Renderer renderer)
|
||||
{
|
||||
ImGui.Render();
|
||||
RenderCommandLists(ImGui.GetDrawData());
|
||||
}
|
||||
|
||||
private IntPtr _context;
|
||||
private Texture2D _fontTexture;
|
||||
private Vector2 _scaleFactor = Vector2.One;
|
||||
}
|
||||
}
|
||||
153
Voile/Source/Utils/Logger.cs
Normal file
153
Voile/Source/Utils/Logger.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
#pragma warning disable CA2211
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Voile.Utils
|
||||
{
|
||||
public class Logger
|
||||
{
|
||||
public static Action<string, LogLevel>? OnLog;
|
||||
public static string LogPath { get; set; } = "Logs/";
|
||||
|
||||
/// <summary>
|
||||
/// Maximum amount of log files in a log folder. If it reaches the limit, all logs will be written to <c>voile-latest.log</c> instead of creating a new one.
|
||||
/// </summary>
|
||||
public static int MaxLogFiles { get; set; } = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the logging level. In release builds, the log level is <c>Error</c>. In debug, the log level is <c>Echo</c>.
|
||||
/// </summary>
|
||||
public static LogLevel LogLevel = LogLevel.Error;
|
||||
/// <summary>
|
||||
/// Specifies if the logger should write to file.
|
||||
/// </summary>
|
||||
public static bool WriteToFile = true;
|
||||
|
||||
public Logger(string className)
|
||||
{
|
||||
_className = className;
|
||||
|
||||
if (WriteToFile && !_logCreated)
|
||||
{
|
||||
var dirInfo = Directory.CreateDirectory(LogPath);
|
||||
var files = dirInfo.GetFiles();
|
||||
|
||||
string logName = $"voile-{DateFormat}-{TimeFormat}.log".Replace(':', '.');
|
||||
|
||||
if (files.Length >= MaxLogFiles)
|
||||
{
|
||||
logName = "voile-latest.log";
|
||||
}
|
||||
|
||||
var path = Path.Combine(LogPath, logName);
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
|
||||
_fileStream = File.Create(path);
|
||||
_fileWriter = new StreamWriter(_fileStream);
|
||||
_logCreated = true;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
LogLevel = LogLevel.Echo;
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Echo(string what)
|
||||
{
|
||||
string message = string.Format(EchoFormat, TimeFormat, what);
|
||||
|
||||
LogFile(message);
|
||||
LogConsole(what, LogLevel.Echo);
|
||||
OnLog?.Invoke(what, LogLevel.Echo);
|
||||
}
|
||||
|
||||
public void Info(string what, [CallerMemberName] string method = "")
|
||||
{
|
||||
LogLevel logType = LogLevel.Info;
|
||||
string message = string.Format(LogFormat, TimeFormat, logType.ToString(), _className, method, what);
|
||||
|
||||
LogFile(message);
|
||||
LogConsole(message, logType);
|
||||
OnLog?.Invoke(message, logType);
|
||||
}
|
||||
|
||||
public void Warn(string what, [CallerMemberName] string method = "")
|
||||
{
|
||||
LogLevel logType = LogLevel.Warn;
|
||||
string message = string.Format(LogFormat, TimeFormat, logType.ToString(), _className, method, what);
|
||||
|
||||
LogFile(message);
|
||||
LogConsole(message, logType);
|
||||
OnLog?.Invoke(message, logType);
|
||||
}
|
||||
|
||||
public void Error(string what, [CallerMemberName] string method = "")
|
||||
{
|
||||
LogLevel logType = LogLevel.Error;
|
||||
string message = string.Format(LogFormat, TimeFormat, logType.ToString(), _className, method, what);
|
||||
|
||||
LogFile(message);
|
||||
LogConsole(message, logType);
|
||||
OnLog?.Invoke(message, logType);
|
||||
}
|
||||
|
||||
|
||||
private static string TimeFormat => $"{DateTime.Now:HH:mm:ffff}";
|
||||
private static string DateFormat => $"{DateTime.Now:d:M:yyyy:}";
|
||||
private static string LogFormat => "({0}) [{1}/{2}/{3}] {4}";
|
||||
private static string EchoFormat => "({0}) {1}";
|
||||
|
||||
private static void LogConsole(string what, LogLevel logType)
|
||||
{
|
||||
if (LogLevel < logType) return;
|
||||
Console.ForegroundColor = ConsoleColorForLog(logType);
|
||||
Console.WriteLine(what);
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
}
|
||||
|
||||
private static ConsoleColor ConsoleColorForLog(LogLevel logType)
|
||||
{
|
||||
ConsoleColor color = ConsoleColor.White;
|
||||
|
||||
switch (logType)
|
||||
{
|
||||
case LogLevel.Info:
|
||||
color = ConsoleColor.Cyan;
|
||||
break;
|
||||
|
||||
case LogLevel.Warn:
|
||||
color = ConsoleColor.Yellow;
|
||||
break;
|
||||
case LogLevel.Error:
|
||||
color = ConsoleColor.Red;
|
||||
break;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
private void LogFile(string message)
|
||||
{
|
||||
if (!WriteToFile || _fileWriter is null) return;
|
||||
_fileWriter.WriteLine(message);
|
||||
_fileWriter.Flush();
|
||||
}
|
||||
|
||||
private readonly string _className;
|
||||
private static bool _logCreated;
|
||||
private static FileStream? _fileStream;
|
||||
private static StreamWriter? _fileWriter;
|
||||
}
|
||||
|
||||
public enum LogLevel
|
||||
{
|
||||
Error,
|
||||
Warn,
|
||||
Info,
|
||||
Echo,
|
||||
}
|
||||
}
|
||||
48
Voile/Source/Utils/MathUtils.cs
Normal file
48
Voile/Source/Utils/MathUtils.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace Voile
|
||||
{
|
||||
public static class MathUtils
|
||||
{
|
||||
public static float Lerp(float a, float b, double t) => a + (b - a) * (float)t;
|
||||
|
||||
public static Color LerpColor(Color colorA, Color colorB, double t)
|
||||
{
|
||||
var r = (byte)Lerp(colorA.R, colorB.R, t);
|
||||
var g = (byte)Lerp(colorA.G, colorB.G, t);
|
||||
var b = (byte)Lerp(colorA.B, colorB.B, t);
|
||||
var a = (byte)Lerp(colorA.A, colorB.A, t);
|
||||
|
||||
return new Color(r, g, b, a);
|
||||
}
|
||||
|
||||
public static Vector2 RandomVector2(Vector2 min, Vector2 max)
|
||||
{
|
||||
var x = _random.NextDouble(min.X, max.X);
|
||||
var y = _random.NextDouble(min.Y, max.Y);
|
||||
|
||||
return new Vector2((float)x, (float)y);
|
||||
}
|
||||
|
||||
public static float EaseOutBack(float x)
|
||||
{
|
||||
var c1 = 1.70158f;
|
||||
var c3 = c1 + 1f;
|
||||
|
||||
return 1f + c3 * (float)Math.Pow(x - 1f, 3f) + c1 * (float)Math.Pow(x - 1, 2);
|
||||
}
|
||||
|
||||
public static float EaseOutElastic(float x)
|
||||
{
|
||||
var c4 = 2f * (float)Math.PI / 3f;
|
||||
|
||||
return x == 0
|
||||
? 0
|
||||
: x == 1
|
||||
? 1
|
||||
: (float)Math.Pow(2, -10 * x) * (float)Math.Sin((x * 10 - 0.75f) * c4) + 1;
|
||||
}
|
||||
|
||||
private static LehmerRandom _random = new();
|
||||
}
|
||||
}
|
||||
43
Voile/TODO.md
Normal file
43
Voile/TODO.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# RogueMine
|
||||
|
||||
## Engine
|
||||
|
||||
### Core
|
||||
- Virtual file system
|
||||
- Hot reloading
|
||||
|
||||
### Rendering
|
||||
|
||||
- ~~API for drawing textured quads~~
|
||||
- ~~Camera API~~
|
||||
- Switch to custom rendering (Veldrid?)
|
||||
|
||||
### Audio
|
||||
|
||||
- ~~Integrate FMOD~~
|
||||
- 2D audio abstraction
|
||||
|
||||
### Misc
|
||||
|
||||
- ~~Asset manager~~
|
||||
- ~~Separate engine and game into separate projects~~
|
||||
- ~~Particle system~~
|
||||
|
||||
### SceneGraph module
|
||||
|
||||
- ~~Layers (sorting mechanism)~~
|
||||
- Save/load scenes from file
|
||||
|
||||
## Input
|
||||
|
||||
- ~~Action system~~
|
||||
- Gamepad support
|
||||
|
||||
### UI
|
||||
|
||||
- Basic containers
|
||||
- MarginContainer
|
||||
- Text wrapping
|
||||
- UIElement 2.0
|
||||
- Interaction
|
||||
- Callbacks
|
||||
25
Voile/Voile.csproj
Normal file
25
Voile/Voile.csproj
Normal file
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Voile.Fmod" Version="0.2.2.8" />
|
||||
<PackageReference Include="ImGui.NET" Version="1.89.4" />
|
||||
<PackageReference Include="Raylib-cs" Version="4.2.0.1" />
|
||||
<PackageReference Include="SharpFont" Version="4.0.1" />
|
||||
<PackageReference Include="Silk.NET" Version="2.17.0" />
|
||||
<PackageReference Include="SixLabors.Fonts" Version="1.0.0-beta19" />
|
||||
<PackageReference Include="StbImageSharp" Version="2.27.13" />
|
||||
<PackageReference Include="StbVorbisSharp" Version="1.22.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="BuildFmod" BeforeTargets="BeforeBuild">
|
||||
<MSBuild Projects="../Voile.Fmod/Voile.Fmod.csproj" Targets="Restore;Build" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user