TomlDataReader, documentation updates, move ParticleSystem to Voile.Systems.Particles.
This commit is contained in:
@@ -26,11 +26,11 @@ namespace Voile.Audio
|
||||
|
||||
int channels = 0;
|
||||
|
||||
if (sound.Format == SoundFormat.Mono)
|
||||
if (sound.Channel == SoundChannel.Mono)
|
||||
{
|
||||
channels = 1;
|
||||
}
|
||||
else if (sound.Format == SoundFormat.Stereo)
|
||||
else if (sound.Channel == SoundChannel.Stereo)
|
||||
{
|
||||
channels = 2;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ using Voile.Resources;
|
||||
|
||||
namespace Voile
|
||||
{
|
||||
/// <summary>
|
||||
/// Entry point for the Voile game.
|
||||
/// </summary>
|
||||
public abstract class Game
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -4,7 +4,7 @@ using Raylib_cs;
|
||||
namespace Voile.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// An input system implemented using Raylib's API. Used by default together with <see cref="RaylibRenderSystem"/>.
|
||||
/// An input system implemented using Raylib's API. Used by default together with <see cref="Rendering.RaylibRenderSystem"/>.
|
||||
/// </summary>
|
||||
public class RaylibInputSystem : InputSystem
|
||||
{
|
||||
|
||||
@@ -123,7 +123,8 @@ namespace Voile.Rendering
|
||||
|
||||
public override void BeginBlended(BlendMode blendMode)
|
||||
{
|
||||
Raylib.BeginBlendMode((Raylib_cs.BlendMode)blendMode);
|
||||
var rayBlend = (Raylib_cs.BlendMode)blendMode;
|
||||
Raylib.BeginBlendMode(rayBlend);
|
||||
}
|
||||
|
||||
public override void EndBlended()
|
||||
|
||||
76
Voile/Source/Resources/DataReaders/IDataReader.cs
Normal file
76
Voile/Source/Resources/DataReaders/IDataReader.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace Voile.Resources.DataReaders
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads data from a specified stream.
|
||||
/// </summary>
|
||||
public interface IStreamDataReader
|
||||
{
|
||||
/// <summary>
|
||||
/// Read data from a specified stream.
|
||||
/// </summary>
|
||||
/// <param name="data">Stream with data.</param>
|
||||
void Read(Stream data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates data integrity or schema.
|
||||
/// </summary>
|
||||
public interface IDataValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines if data specified is valid and can be safely read.
|
||||
/// </summary>
|
||||
/// <returns>Returns true if data is valid.</returns>
|
||||
bool Valid();
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets primitive type data from a key/value based format.
|
||||
/// </summary>
|
||||
public interface IStreamKeyValueGetter
|
||||
{
|
||||
/// <summary>
|
||||
/// Get an int from this data getter.
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the value.</param>
|
||||
/// <param name="defaultValue">Default value in case this getter fails to get data.</param>
|
||||
/// <returns></returns>
|
||||
int GetInt(string key, int defaultValue = 0);
|
||||
/// <summary>
|
||||
/// Get a long from this data getter.
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the value.</param>
|
||||
/// <param name="defaultValue">Default value in case this getter fails to get data.</param>
|
||||
/// <returns></returns>
|
||||
long GetLong(string key, long defaultValue = 0);
|
||||
/// <summary>
|
||||
/// Get a float from this data getter.
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the value.</param>
|
||||
/// <param name="defaultValue">Default value in case this getter fails to get data.</param>
|
||||
/// <returns></returns>
|
||||
float GetFloat(string key, float defaultValue = 0.0f);
|
||||
/// <summary>
|
||||
/// Get a double from this data getter.
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the value.</param>
|
||||
/// <param name="defaultValue">Default value in case this getter fails to get data.</param>
|
||||
/// <returns></returns>
|
||||
double GetDouble(string key, double defaultValue = 0.0);
|
||||
/// <summary>
|
||||
/// Get a Voile.Color from this data getter.
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the value.</param>
|
||||
/// <param name="defaultValue">Default value in case this getter fails to get data.</param>
|
||||
/// <returns></returns>
|
||||
Color GetColor(string key, Color defaultValue);
|
||||
/// <summary>
|
||||
/// Get a System.Numerics.Vector2 from this data getter.
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the value.</param>
|
||||
/// <param name="defaultValue">Default value in case this getter fails to get data.</param>
|
||||
/// <returns></returns>
|
||||
Vector2 GetVector2(string key, Vector2 defaultValue);
|
||||
}
|
||||
}
|
||||
218
Voile/Source/Resources/DataReaders/TomlDataReader.cs
Normal file
218
Voile/Source/Resources/DataReaders/TomlDataReader.cs
Normal file
@@ -0,0 +1,218 @@
|
||||
|
||||
using System.Numerics;
|
||||
using Tommy;
|
||||
|
||||
namespace Voile.Resources.DataReaders;
|
||||
|
||||
/// <summary>
|
||||
/// Reads key/value data from a TOML file.
|
||||
/// </summary>
|
||||
public class TomlDataReader : IStreamDataReader, IDataValidator, IStreamKeyValueGetter, IDisposable
|
||||
{
|
||||
public string ExpectedHeader { get; private set; } = string.Empty;
|
||||
public TomlDataReader(string expectedHeader)
|
||||
{
|
||||
ExpectedHeader = expectedHeader;
|
||||
}
|
||||
|
||||
public void Read(Stream data)
|
||||
{
|
||||
if (data is not FileStream fs)
|
||||
{
|
||||
throw new ArgumentException("Toml data reader only supports file streams.");
|
||||
}
|
||||
|
||||
_fs = fs;
|
||||
|
||||
using (var reader = new StreamReader(_fs))
|
||||
{
|
||||
_table = TOML.Parse(reader);
|
||||
_valid = _table.HasKey(ExpectedHeader);
|
||||
}
|
||||
}
|
||||
|
||||
public int GetInt(string key, int defaultValue = 0)
|
||||
{
|
||||
if (_table is null)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
var dataTable = _table[ExpectedHeader];
|
||||
|
||||
if (!dataTable.HasKey(key))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
var tableValue = dataTable[key];
|
||||
|
||||
if (!tableValue.IsInteger)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return tableValue.AsInteger;
|
||||
}
|
||||
|
||||
public long GetLong(string key, long defaultValue = 0)
|
||||
{
|
||||
if (_table is null)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
var dataTable = _table[ExpectedHeader];
|
||||
|
||||
if (!dataTable.HasKey(key))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
var tableValue = dataTable[key];
|
||||
|
||||
if (!tableValue.IsInteger)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return tableValue.AsInteger.Value;
|
||||
}
|
||||
|
||||
public float GetFloat(string key, float defaultValue = 0)
|
||||
{
|
||||
if (_table is null)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
var dataTable = _table[ExpectedHeader];
|
||||
|
||||
if (!dataTable.HasKey(key))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
var tableValue = dataTable[key];
|
||||
|
||||
if (!tableValue.IsFloat)
|
||||
{
|
||||
if (tableValue.IsInteger) return (float)tableValue.AsInteger.Value;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return tableValue.AsFloat;
|
||||
}
|
||||
|
||||
public double GetDouble(string key, double defaultValue = 0)
|
||||
{
|
||||
if (_table is null)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
var dataTable = _table[ExpectedHeader];
|
||||
|
||||
if (!dataTable.HasKey(key))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
if (!dataTable.IsFloat)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return dataTable.AsFloat.Value;
|
||||
}
|
||||
|
||||
public Color GetColor(string key, Color defaultValue)
|
||||
{
|
||||
if (_table is null)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
var dataTable = _table[ExpectedHeader];
|
||||
|
||||
if (!dataTable.HasKey(key))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
var colorNode = dataTable[key];
|
||||
|
||||
if (colorNode.IsInteger)
|
||||
{
|
||||
return new Color(colorNode.AsInteger);
|
||||
}
|
||||
else if (colorNode.IsArray)
|
||||
{
|
||||
var colorArray = colorNode.AsArray;
|
||||
|
||||
var rNode = colorArray[0];
|
||||
var gNode = colorArray[1];
|
||||
var bNode = colorArray[2];
|
||||
|
||||
var r = rNode.IsInteger ? rNode.AsInteger : 0;
|
||||
var g = gNode.IsInteger ? gNode.AsInteger : 0;
|
||||
var b = bNode.IsInteger ? bNode.AsInteger : 0;
|
||||
|
||||
int a = 255;
|
||||
|
||||
if (colorArray.RawArray.Count == 4)
|
||||
{
|
||||
var aNode = colorArray[3];
|
||||
a = aNode.IsInteger ? aNode.AsInteger : 0;
|
||||
}
|
||||
|
||||
return new Color((byte)r, (byte)g, (byte)b, (byte)a);
|
||||
}
|
||||
else if (colorNode.IsString)
|
||||
{
|
||||
var colorHexString = colorNode.AsString.Value;
|
||||
return Color.FromHexString(colorHexString);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Color can only be represented as an array of integers in the range of 0-255, array of floats (0-1), hexadecimal, or hex string.");
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 GetVector2(string key, Vector2 defaultValue)
|
||||
{
|
||||
if (_table is null)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
var dataTable = _table[ExpectedHeader];
|
||||
|
||||
if (!dataTable.HasKey(key))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
var vector2Node = dataTable[key];
|
||||
|
||||
if (!vector2Node.IsArray)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
var vector2Array = vector2Node.AsArray;
|
||||
|
||||
return new Vector2(vector2Array[0], vector2Array[1]);
|
||||
}
|
||||
|
||||
public bool Valid() => _valid;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_fs?.Dispose();
|
||||
}
|
||||
|
||||
private TomlTable? _table;
|
||||
private FileStream? _fs;
|
||||
private bool _valid;
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
namespace Voile;
|
||||
|
||||
/// <summary>
|
||||
/// Represents font data.
|
||||
/// </summary>
|
||||
public class Font : Resource
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -3,10 +3,22 @@ using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Voile.Resources
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads resources from various sources and prepares them to be used for Voile.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public abstract class ResourceLoader<T> where T : Resource
|
||||
{
|
||||
/// <summary>
|
||||
/// File extensions that are supported by this loader.
|
||||
/// </summary>
|
||||
public abstract IEnumerable<string> SupportedExtensions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Loads a resource to this resource loader's resource list.
|
||||
/// </summary>
|
||||
/// <param name="path">File system path to the resource to load.</param>
|
||||
/// <returns>A <see cref="Guid"/> of the loaded resource that can be later retrieved with <see cref="TryGet"/>.</returns>
|
||||
public Guid Load(string path)
|
||||
{
|
||||
var resource = LoadResource(path);
|
||||
@@ -25,6 +37,9 @@ namespace Voile.Resources
|
||||
return guid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reloads resources loaded by this resource loader.
|
||||
/// </summary>
|
||||
public void Reload()
|
||||
{
|
||||
foreach (var loadedResource in _loadedResources)
|
||||
@@ -33,6 +48,12 @@ namespace Voile.Resources
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a resource from a GUID.
|
||||
/// </summary>
|
||||
/// <param name="resourceGuid">GUID of the resource to get.</param>
|
||||
/// <param name="resource">Retrieved resource. Will return a default resource if resource retrieval was not successful.</param>
|
||||
/// <returns>True if resource retrieval was successful.</returns>
|
||||
public bool TryGet(Guid resourceGuid, [NotNullWhen(true)] out T? resource)
|
||||
{
|
||||
resource = default;
|
||||
@@ -47,6 +68,11 @@ namespace Voile.Resources
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unloads a resource.
|
||||
/// </summary>
|
||||
/// <param name="resourceGuid">GUID of a resource to unload.</param>
|
||||
/// <returns>True if unloading was successful, otherwise false.</returns>
|
||||
public bool TryUnload(Guid resourceGuid)
|
||||
{
|
||||
if (!_loadedResources.ContainsKey(resourceGuid))
|
||||
@@ -62,6 +88,11 @@ namespace Voile.Resources
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load steps specific to this resource loader.
|
||||
/// </summary>
|
||||
/// <param name="path">File system path to the resource to load.</param>
|
||||
/// <returns>Loaded resource.</returns>
|
||||
protected abstract T LoadResource(string path);
|
||||
|
||||
protected Dictionary<Guid, T> _loadedResources = new();
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace Voile.Resources
|
||||
|
||||
result = new Sound(path, audioData)
|
||||
{
|
||||
Format = (SoundFormat)vorbis.Channels - 1,
|
||||
Channel = (SoundChannel)vorbis.Channels - 1,
|
||||
SampleRate = vorbis.SampleRate,
|
||||
BufferSize = length
|
||||
};
|
||||
|
||||
@@ -3,6 +3,9 @@ using StbImageSharp;
|
||||
|
||||
namespace Voile
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads a 2D texture from PNG or JPG files using StbImageSharp.
|
||||
/// </summary>
|
||||
public class Texture2dLoader : ResourceLoader<Texture2d>
|
||||
{
|
||||
public override IEnumerable<string> SupportedExtensions => new string[]
|
||||
|
||||
@@ -8,8 +8,14 @@ namespace Voile
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public sealed class ResourceRef<T> where T : Resource
|
||||
{
|
||||
/// <summary>
|
||||
/// Resource GUID this ResourceRef maps to.
|
||||
/// </summary>
|
||||
public readonly Guid Guid = Guid.Empty;
|
||||
public bool HasValue => Guid != Guid.Empty;
|
||||
/// <summary>
|
||||
/// Retrieve a reference.
|
||||
/// </summary>
|
||||
public T Value => ResourceManager.GetResource<T>(Guid);
|
||||
|
||||
public ResourceRef(Guid guid)
|
||||
@@ -18,8 +24,14 @@ namespace Voile
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents data usable by Voile.
|
||||
/// </summary>
|
||||
public abstract class Resource : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Path to this resource.
|
||||
/// </summary>
|
||||
public string Path { get; private set; } = string.Empty;
|
||||
|
||||
public Resource(string path)
|
||||
|
||||
@@ -4,14 +4,36 @@ using Voile.Utils;
|
||||
|
||||
namespace Voile.Resources
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages resource loading and lifetime, and wraps around multiple available ResourceLoaders.
|
||||
/// </summary>
|
||||
public class ResourceManager : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Root path for resources to manipulate resources at.
|
||||
/// </summary>
|
||||
public static string ResourceRoot { get; set; } = "Resources/";
|
||||
|
||||
/// <summary>
|
||||
/// Emits when a resource gets loaded.
|
||||
/// </summary>
|
||||
public static Action<string>? OnLoadRequested;
|
||||
/// <summary>
|
||||
/// Emits when a resource gets unloaded.
|
||||
/// </summary>
|
||||
public static Action<Guid>? OnUnloadRequested;
|
||||
/// <summary>
|
||||
/// Emits when <see cref="ResourceManager"/> requested reload of all loaded resources.
|
||||
/// </summary>
|
||||
public static Action? OnReloaded;
|
||||
|
||||
/// <summary>
|
||||
/// Loads a resource from a given file system path.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Resource type to load</typeparam>
|
||||
/// <param name="path">File system path to the resource to load.</param>
|
||||
/// <param name="result">A ResourceRef for the loaded resource.</param>
|
||||
/// <returns>True if resource was loaded successfully, otherwise will log an error and return false.</returns>
|
||||
public static bool TryLoad<T>(string path, [NotNullWhen(true)] out ResourceRef<T>? result) where T : Resource
|
||||
{
|
||||
T? resource = default;
|
||||
@@ -59,6 +81,9 @@ namespace Voile.Resources
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reload all resources that are currently loaded.
|
||||
/// </summary>
|
||||
public void Reload()
|
||||
{
|
||||
_logger.Info("Reloading resources.");
|
||||
@@ -112,17 +137,24 @@ namespace Voile.Resources
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetResource<T>(string resourceId, [NotNullWhen(true)] out T? resource) where T : Resource
|
||||
/// <summary>
|
||||
/// Gets a <see cref="Resource"/> by a file system path.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of <see cref="Resource"/> to load.</typeparam>
|
||||
/// <param name="path">Path to the resource.</param>
|
||||
/// <param name="resource">Retrieved resource. Otherwise null if nothing got retrieved.</param>
|
||||
/// <returns>True if resource got successfully retrieved, otherwise false.</returns>
|
||||
public bool TryGetResource<T>(string path, [NotNullWhen(true)] out T? resource) where T : Resource
|
||||
{
|
||||
resource = null;
|
||||
|
||||
if (!IsResourceLoaded(resourceId))
|
||||
if (!IsResourceLoaded(path))
|
||||
{
|
||||
_logger.Error($"Resource \"{resourceId}\" has not yet been loaded!");
|
||||
_logger.Error($"Resource \"{path}\" has not yet been loaded!");
|
||||
return false;
|
||||
}
|
||||
|
||||
var resourceGuid = _resourcePathMap[resourceId];
|
||||
var resourceGuid = _resourcePathMap[path];
|
||||
|
||||
if (!TryGetLoader(out ResourceLoader<T>? loader))
|
||||
{
|
||||
@@ -132,7 +164,7 @@ namespace Voile.Resources
|
||||
|
||||
if (!loader.TryGet(resourceGuid, out T? loadedResource))
|
||||
{
|
||||
_logger.Error($"No resource with id {resourceId} found!");
|
||||
_logger.Error($"No resource with id {path} found!");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -141,6 +173,13 @@ namespace Voile.Resources
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="Resource"/> by resource's <see cref="Guid"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of <see cref="Resource"/> to load.</typeparam>
|
||||
/// <param name="resourceGuid">Resource's GUID.</param>
|
||||
/// <param name="resource">Retrieved resource. Otherwise null if nothing got retrieved.</param>
|
||||
/// <returns>True if resource got successfully retrieved, otherwise false.</returns>
|
||||
public static T GetResource<T>(Guid resourceGuid) where T : Resource
|
||||
{
|
||||
if (!TryGetLoader(out ResourceLoader<T>? loader))
|
||||
@@ -155,9 +194,18 @@ namespace Voile.Resources
|
||||
|
||||
return loadedResource;
|
||||
}
|
||||
/// <summary>
|
||||
/// Determines if a resource is currently loaded.
|
||||
/// </summary>
|
||||
/// <param name="path">Path to the resource.</param>
|
||||
/// <returns>True if a resource at the specified path is loaded, otherwise false.</returns>
|
||||
public bool IsResourceLoaded(string path) => _resourcePathMap.ContainsKey(path);
|
||||
|
||||
public bool IsResourceLoaded(string resourceId) => _resourcePathMap.ContainsKey(resourceId);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a resource loader associated with a resource type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">A type of <see cref="Resource"/>.</typeparam>
|
||||
/// <param name="loader">Loader to use for loading this resource type.</param>
|
||||
public static void AddResourceLoaderAssociation<T>(ResourceLoader<T> loader) where T : Resource
|
||||
{
|
||||
_logger.Info($"Added resource loader association for {typeof(T)}.");
|
||||
@@ -167,12 +215,20 @@ namespace Voile.Resources
|
||||
_resourceLoaderAssociations.Add(typeof(T), loader);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a resource saver associated with a resource type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">A type of <see cref="Resource"/>.</typeparam>
|
||||
/// <param name="saver">saver to use for saving this resource type.</param>
|
||||
public void AddResourceSaverAssociation<T>(IResourceSaver<T> saver) where T : Resource
|
||||
{
|
||||
_logger.Info($"Added resource saver association for {typeof(T)}.");
|
||||
_resourceSaverAssociations.Add(typeof(T), saver);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables file watching. <see cref="ResourceManager"/> will automaticallly reload resources when they get changed.
|
||||
/// </summary>
|
||||
public void EnableFileWatching()
|
||||
{
|
||||
_fileWatcher = new FileSystemWatcher(ResourceRoot);
|
||||
@@ -252,15 +308,9 @@ namespace Voile.Resources
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// foreach (var loader in _)
|
||||
// {
|
||||
// TryUnload(resource.Key);
|
||||
// }
|
||||
|
||||
// GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
|
||||
private static Logger _logger = new(nameof(ResourceManager));
|
||||
|
||||
private static readonly Dictionary<Type, object> _resourceLoaderAssociations = new()
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
namespace Voile
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents raw audio samples.
|
||||
/// </summary>
|
||||
public class Sound : Resource
|
||||
{
|
||||
public SoundFormat Format { get; set; }
|
||||
public SoundChannel Channel { get; set; }
|
||||
public int SampleRate { get; set; }
|
||||
|
||||
public byte[]? Buffer { get; private set; }
|
||||
@@ -14,7 +17,10 @@ namespace Voile
|
||||
}
|
||||
}
|
||||
|
||||
public enum SoundFormat
|
||||
/// <summary>
|
||||
/// Channel type contained within a sound.
|
||||
/// </summary>
|
||||
public enum SoundChannel
|
||||
{
|
||||
Mono,
|
||||
Stereo
|
||||
|
||||
18
Voile/Source/Resources/TextDataResource.cs
Normal file
18
Voile/Source/Resources/TextDataResource.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace Voile.Resources;
|
||||
|
||||
public class TextDataResource : Resource
|
||||
{
|
||||
public TextDataResource(string path) : base(path)
|
||||
{
|
||||
}
|
||||
|
||||
public void AddValue<T>(string key, T value) where T : struct
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void GetValue<T>(string key, T value) where T : struct
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
namespace Voile
|
||||
{
|
||||
/// <summary>
|
||||
/// A two-dimensional texture stored on the GPU.
|
||||
/// </summary>
|
||||
public class Texture2d : Resource
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -19,5 +19,9 @@ public interface IStartableSystem<T>
|
||||
|
||||
public interface IUpdatableSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Updates this system.
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">Time step.</param>
|
||||
void Update(double deltaTime);
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
using System.ComponentModel;
|
||||
using System.Numerics;
|
||||
using Tommy;
|
||||
using Voile.Resources;
|
||||
using Voile.Resources.DataReaders;
|
||||
using Voile.Utils;
|
||||
|
||||
namespace Voile.Systems;
|
||||
namespace Voile.Systems.Particles;
|
||||
|
||||
public struct Particle
|
||||
{
|
||||
@@ -53,6 +52,9 @@ public class ParticleEmitterSettingsResource : Resource
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads <see cref="ParticleEmitterSettingsResource"/> from a provided TOML data file.
|
||||
/// </summary>
|
||||
public class ParticleEmitterSettingsResourceLoader : ResourceLoader<ParticleEmitterSettingsResource>
|
||||
{
|
||||
public override IEnumerable<string> SupportedExtensions => new string[] {
|
||||
@@ -63,45 +65,57 @@ public class ParticleEmitterSettingsResourceLoader : ResourceLoader<ParticleEmit
|
||||
{
|
||||
// TODO: this is ugly, better to make some sort of wrapper API for TOML files.
|
||||
var settings = new ParticleEmitterSettings();
|
||||
// Parse into a node
|
||||
using (StreamReader reader = File.OpenText(path))
|
||||
|
||||
using (var reader = new TomlDataReader("ParticleEmitterSettings"))
|
||||
{
|
||||
// Parse the table
|
||||
TomlTable table = TOML.Parse(reader);
|
||||
|
||||
if (!table.HasKey("ParticleEmitterSettings"))
|
||||
{
|
||||
Console.WriteLine("Particle emitter settings doesnt have a header!");
|
||||
}
|
||||
|
||||
if (table["ParticleEmitterSettings"]["MaxParticles"] is TomlInteger maxParticles)
|
||||
{
|
||||
settings.MaxParticles = (int)maxParticles.Value;
|
||||
}
|
||||
|
||||
if (table["ParticleEmitterSettings"]["EmitRadius"] is TomlInteger emitRadius)
|
||||
{
|
||||
settings.EmitRadius = (int)emitRadius.Value;
|
||||
}
|
||||
|
||||
if (table["ParticleEmitterSettings"]["LifeTime"] is TomlFloat lifetime)
|
||||
{
|
||||
settings.LifeTime = (float)lifetime.Value;
|
||||
}
|
||||
reader.Read(File.Open(path, FileMode.Open));
|
||||
|
||||
settings.MaxParticles = reader.GetInt("MaxParticles");
|
||||
settings.EmitRadius = reader.GetInt("EmitRadius");
|
||||
settings.LifeTime = reader.GetFloat("LifeTime", 1.0f);
|
||||
settings.Explosiveness = reader.GetFloat("Explosiveness");
|
||||
settings.Direction = reader.GetVector2("Direction", Vector2.Zero);
|
||||
settings.LinearVelocity = reader.GetFloat("LinearVelocity", 980f);
|
||||
settings.AngularVelocity = reader.GetFloat("AngularVelocity");
|
||||
settings.AngularVelocityRandom = reader.GetFloat("AngularVelocityRandom", 1.0f);
|
||||
settings.LinearVelocityRandom = reader.GetFloat("LinearVelocityRandom", 1.0f);
|
||||
settings.Gravity = reader.GetVector2("Gravity", Vector2.UnitY * 980f);
|
||||
settings.ScaleBegin = reader.GetFloat("ScaleBegin");
|
||||
settings.ScaleEnd = reader.GetFloat("ScaleEnd");
|
||||
settings.ColorBegin = reader.GetColor("ColorBegin", Color.White);
|
||||
settings.ColorEnd = reader.GetColor("ColorEnd", Color.Black);
|
||||
settings.Damping = reader.GetFloat("Damping", 1.0f);
|
||||
}
|
||||
|
||||
return new ParticleEmitterSettingsResource(path, settings);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits and simulates particles from a provided particle segment.
|
||||
/// </summary>
|
||||
public class ParticleEmitter : IUpdatableSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// A segment of particles this emitter simulates.
|
||||
/// </summary>
|
||||
public ReadOnlySpan<Particle> Particles => _particles.AsSpan();
|
||||
/// <summary>
|
||||
/// Origin position in the world of this emitter.
|
||||
/// </summary>
|
||||
public Vector2 OriginPosition => _originPosition;
|
||||
/// <summary>
|
||||
/// <see cref="ParticleEmitterSettings"/> for this emitter.
|
||||
/// </summary>
|
||||
public ParticleEmitterSettings Settings => _settingsResource.Value.Settings;
|
||||
public int ParticleArrayOffset => _particles.Offset;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new <see cref="ParticleEmitter"/>.
|
||||
/// </summary>
|
||||
/// <param name="originPosition">World origin position.</param>
|
||||
/// <param name="settingsResource">Emitter settings resource.</param>
|
||||
/// <param name="particles">Particle segment that this emitter will simulate.</param>
|
||||
public ParticleEmitter(Vector2 originPosition, ResourceRef<ParticleEmitterSettingsResource> settingsResource, ArraySegment<Particle> particles)
|
||||
{
|
||||
_originPosition = originPosition;
|
||||
@@ -115,13 +129,12 @@ public class ParticleEmitter : IUpdatableSystem
|
||||
_random = new LehmerRandom();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restart this emitter.
|
||||
/// </summary>
|
||||
/// <param name="particles">New particle segment.</param>
|
||||
public void Restart(ArraySegment<Particle> particles)
|
||||
{
|
||||
// foreach (var particle in _particles)
|
||||
// {
|
||||
// particle.LifeTimeRemaining = 0.0f;
|
||||
// }
|
||||
|
||||
for (int i = 0; i < _particles.Count; i++)
|
||||
{
|
||||
var particle = _particles[i];
|
||||
@@ -134,6 +147,10 @@ public class ParticleEmitter : IUpdatableSystem
|
||||
_particleIndex = _maxParticles - 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates this emitter's simulation.
|
||||
/// </summary>
|
||||
/// <param name="deltaTime"></param>
|
||||
public void Update(double deltaTime)
|
||||
{
|
||||
var rate = (int)MathUtils.Lerp(1, _maxParticles, Settings.Explosiveness);
|
||||
@@ -146,13 +163,6 @@ public class ParticleEmitter : IUpdatableSystem
|
||||
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 = Math.Clamp(particle.LifeTimeRemaining - (float)deltaTime, 0.0f, particle.LifeTime);
|
||||
|
||||
@@ -215,6 +225,9 @@ public class ParticleEmitter : IUpdatableSystem
|
||||
private ResourceRef<ParticleEmitterSettingsResource> _settingsResource;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CPU based particle simulator.
|
||||
/// </summary>
|
||||
public class ParticleSystem : IUpdatableSystem, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
@@ -222,14 +235,26 @@ public class ParticleSystem : IUpdatableSystem, IDisposable
|
||||
/// </summary>
|
||||
public int ParticleLimit { get; set; } = 8192;
|
||||
|
||||
/// <summary>
|
||||
/// List of particle emitters created for this ParticleSystem.
|
||||
/// </summary>
|
||||
public IReadOnlyList<ParticleEmitter> Emitters => _emitters;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new <see cref="ParticleSystem"/>.
|
||||
/// </summary>
|
||||
public ParticleSystem()
|
||||
{
|
||||
_particleIndex = ParticleLimit - 1;
|
||||
_particles = new Particle[ParticleLimit];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an emitter from a <see cref="ParticleEmitterSettingsResource"/>.
|
||||
/// </summary>
|
||||
/// <param name="originPosition"></param>
|
||||
/// <param name="settingsResource"></param>
|
||||
/// <returns></returns>
|
||||
public int CreateEmitter(Vector2 originPosition, ResourceRef<ParticleEmitterSettingsResource> settingsResource)
|
||||
{
|
||||
var settings = settingsResource.Value.Settings;
|
||||
@@ -242,11 +267,6 @@ public class ParticleSystem : IUpdatableSystem, IDisposable
|
||||
|
||||
var particles = new ArraySegment<Particle>(_particles, _emitterSliceOffset, settings.MaxParticles);
|
||||
|
||||
// foreach (var particle in particles)
|
||||
// {
|
||||
// particle.LifeTime = settings.LifeTime;
|
||||
// }
|
||||
|
||||
for (int i = 0; i < particles.Count; i++)
|
||||
{
|
||||
var particle = particles[i];
|
||||
@@ -261,6 +281,11 @@ public class ParticleSystem : IUpdatableSystem, IDisposable
|
||||
return _emitters.Count - 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restarts an emitter.
|
||||
/// </summary>
|
||||
/// <param name="id">Id of an emitter to restart.</param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public void RestartEmitter(int id)
|
||||
{
|
||||
if (id > _emitters.Count - 1)
|
||||
@@ -274,6 +299,10 @@ public class ParticleSystem : IUpdatableSystem, IDisposable
|
||||
emitter.Restart(particles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates this particle simulation. It is recommended to use a fixed timestep for the particle simulation for performance reasons.
|
||||
/// </summary>
|
||||
/// <param name="deltaTime"></param>
|
||||
public void Update(double deltaTime)
|
||||
{
|
||||
foreach (var emitter in _emitters)
|
||||
|
||||
@@ -78,6 +78,29 @@ namespace Voile
|
||||
R = (hex & 0xFF) / 255.0f;
|
||||
}
|
||||
|
||||
public static Color FromHexString(string hex)
|
||||
{
|
||||
if (hex.StartsWith("#"))
|
||||
{
|
||||
hex = hex[1..];
|
||||
}
|
||||
|
||||
if (hex.Length == 6)
|
||||
{
|
||||
int rgb = int.Parse(hex, System.Globalization.NumberStyles.HexNumber);
|
||||
return new Color(rgb);
|
||||
}
|
||||
else if (hex.Length == 8)
|
||||
{
|
||||
int rgba = int.Parse(hex, System.Globalization.NumberStyles.HexNumber);
|
||||
return new Color(rgba);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Invalid hex color format. Use #RRGGBB or #RRGGBBAA.");
|
||||
}
|
||||
}
|
||||
|
||||
public Color Lightened(float amount)
|
||||
{
|
||||
var result = this;
|
||||
|
||||
Reference in New Issue
Block a user