Use bytes for internal RGBA components of Color, iterate particles sequentially in ParticleEmitters, increase limit for CPU particles, reduce size of Particle struct.
This commit is contained in:
11
TODO.md
11
TODO.md
@@ -13,9 +13,9 @@
|
||||
- ~~Use GUIDs and string ID maps for fetching resources instead of string IDs alone.~~
|
||||
- Reimplement unloading.
|
||||
- Finalize ResourceManager and ResourceLoader APIs for 1.0.
|
||||
- Virtual file system.
|
||||
- (stretch goal) Streamed resource loading.
|
||||
- (stretch goal) Add async API for ResourceManager.
|
||||
- (stretch goal) Virtual file system.
|
||||
|
||||
## Serialization
|
||||
|
||||
@@ -41,14 +41,15 @@
|
||||
- ~~Asset manager~~
|
||||
- ~~Separate engine and game into separate projects~~
|
||||
- Particle system 2.0
|
||||
- Reduce the size of Particle struct by infering most parameters through lifetime.
|
||||
- Reduce cache misses by utilizing SoA.
|
||||
- SIMD acceleration.
|
||||
- ~~Reduce the size of Particle struct by infering most parameters through lifetime.~~
|
||||
- ~~Reduce cache misses by utilizing SoA.~~
|
||||
- ~~SIMD acceleration.~~
|
||||
- Dynamically resize particle arrays as emitters get added or removed.
|
||||
|
||||
## SceneGraph module
|
||||
|
||||
- Layers (sorting mechanism)
|
||||
- Full overhaul using Entity Component architecture (not to be mistaken with ECS).
|
||||
- Layers (sorting mechanism)
|
||||
- Save/load scenes from file
|
||||
|
||||
## Input
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
[ParticleEmitterSettings]
|
||||
|
||||
MaxParticles = 8096
|
||||
MaxParticles = 1024
|
||||
EmitRadius = 128
|
||||
Explosiveness = 1.0
|
||||
LifeTime = 1.0
|
||||
Direction = [0.0, 1.0]
|
||||
LifeTime = 2.0
|
||||
Direction = [0.0, -1.0]
|
||||
LinearVelocity = 200
|
||||
Gravity = [0.0, 0.0]
|
||||
LinearVelocityRandom = 0.5
|
||||
ScaleBegin = 1.0
|
||||
ScaleEnd = 0.0
|
||||
ColorBegin = [255, 0, 255, 255]
|
||||
ColorEnd = [0, 0, 0, 0]
|
||||
ScaleEnd = 5.0
|
||||
ColorBegin = [255, 0, 0, 0]
|
||||
ColorEnd = [0, 255, 0, 255]
|
||||
|
||||
@@ -63,6 +63,8 @@ public class TestGame : Game
|
||||
_particleSystem!.Update(Renderer.FrameTime);
|
||||
_particleSimStopwatch.Stop();
|
||||
|
||||
_renderStopwatch = Stopwatch.StartNew();
|
||||
|
||||
Renderer.BeginFrame();
|
||||
Renderer.ClearBackground(Color.Black);
|
||||
foreach (var emitter in _particleSystem!.Emitters)
|
||||
@@ -70,13 +72,17 @@ public class TestGame : Game
|
||||
DrawEmitter(emitter);
|
||||
}
|
||||
|
||||
Renderer.SetTransform(Renderer.WindowSize / 2, Vector2.Zero);
|
||||
Renderer.DrawTexture(_icon, Color.White);
|
||||
|
||||
Renderer.ResetTransform();
|
||||
Renderer.DrawText(_font, $"Particle Sim: {TimeSpan.FromTicks(_particleSimStopwatch.ElapsedTicks).TotalMilliseconds} ms", Color.White);
|
||||
|
||||
Renderer.SetTransform(new Vector2(0.0f, 16.0f), Vector2.Zero);
|
||||
Renderer.DrawText(_font, $"Render: {TimeSpan.FromTicks(_lastRenderTime).TotalMilliseconds} ms", Color.White);
|
||||
|
||||
Renderer.EndFrame();
|
||||
|
||||
_lastRenderTime = _renderStopwatch.ElapsedTicks;
|
||||
|
||||
_renderStopwatch.Restart();
|
||||
_particleSimStopwatch.Restart();
|
||||
}
|
||||
}
|
||||
@@ -90,14 +96,16 @@ public class TestGame : Game
|
||||
{
|
||||
Renderer.BeginBlended(Voile.Rendering.BlendMode.BlendAlpha);
|
||||
|
||||
for (int i = 0; i < emitter.Settings.MaxParticles; i++)
|
||||
var maxParticles = emitter.Settings.MaxParticles;
|
||||
|
||||
for (int i = maxParticles - 1; i > 0; i--)
|
||||
{
|
||||
var particle = emitter.GetParticle(i);
|
||||
|
||||
var color = new Color(particle.ColorArgb);
|
||||
|
||||
Renderer.SetTransform(emitter.OriginPosition + particle.Position, Vector2.Zero);
|
||||
Renderer.DrawCircle(16f * particle.Scale, color);
|
||||
Renderer.DrawRectangle(Vector2.One * 16.0f * particle.Scale, color);
|
||||
}
|
||||
|
||||
Renderer.EndBlended();
|
||||
@@ -105,7 +113,8 @@ public class TestGame : Game
|
||||
|
||||
[NotNull] private ParticleSystem _particleSystem;
|
||||
|
||||
private Stopwatch _particleSimStopwatch;
|
||||
private Stopwatch _particleSimStopwatch, _renderStopwatch;
|
||||
private long _lastRenderTime;
|
||||
private int _emitterId;
|
||||
private ResourceRef<ParticleEmitterSettingsResource> _emitterSettings;
|
||||
private ResourceRef<Font> _font;
|
||||
|
||||
@@ -219,7 +219,8 @@ namespace Voile.Rendering
|
||||
|
||||
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) };
|
||||
var rayColor = new Raylib_cs.Color(color.R, color.G, color.B, color.A);
|
||||
return rayColor;
|
||||
}
|
||||
|
||||
public override void DrawText(ResourceRef<Font> fontResource, string text, Color color)
|
||||
|
||||
@@ -14,8 +14,6 @@ public struct Particle
|
||||
public int EmitterIndex { get; set; }
|
||||
public int ColorArgb { get; set; }
|
||||
public Vector2 Position { get; set; }
|
||||
public Vector2 Velocity { get; set; }
|
||||
public float AngularVelocity { get; set; }
|
||||
public float LifeTime { get; set; } = 1.0f;
|
||||
public float Scale { get; set; }
|
||||
public float Rotation { get; set; }
|
||||
@@ -61,7 +59,6 @@ public class ParticleEmitterSettingsResourceLoader : ResourceLoader<ParticleEmit
|
||||
|
||||
protected override ParticleEmitterSettingsResource LoadResource(string path)
|
||||
{
|
||||
// TODO: this is ugly, better to make some sort of wrapper API for TOML files.
|
||||
var settings = new ParticleEmitterSettings();
|
||||
|
||||
using (var reader = new TomlDataReader("ParticleEmitterSettings"))
|
||||
@@ -116,7 +113,7 @@ public class ParticleEmitter : IUpdatableSystem
|
||||
|
||||
_settingsResource = settingsResource;
|
||||
_maxParticles = _settingsResource.Value.Settings.MaxParticles;
|
||||
_particleIndex = _maxParticles - 1;
|
||||
// _particleIndex = _maxParticles - 1;
|
||||
|
||||
_positions = positionsSlice;
|
||||
_velocities = velocitiesSlice;
|
||||
@@ -131,7 +128,6 @@ public class ParticleEmitter : IUpdatableSystem
|
||||
return new Particle()
|
||||
{
|
||||
Position = _positions[idx],
|
||||
Velocity = _velocities[idx],
|
||||
Scale = MathUtils.Lerp(Settings.ScaleEnd, Settings.ScaleBegin, t),
|
||||
ColorArgb = MathUtils.LerpColor(Settings.ColorEnd, Settings.ColorBegin, t).Argb
|
||||
};
|
||||
@@ -149,7 +145,7 @@ public class ParticleEmitter : IUpdatableSystem
|
||||
}
|
||||
|
||||
_maxParticles = Settings.MaxParticles;
|
||||
_particleIndex = _maxParticles - 1;
|
||||
_particleIndex = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -166,7 +162,6 @@ public class ParticleEmitter : IUpdatableSystem
|
||||
}
|
||||
|
||||
var deltaTimeVector = new Vector2((float)deltaTime);
|
||||
var lifeTimeInv = 1.0f / Settings.LifeTime;
|
||||
var gravityVector = Settings.Gravity * (float)deltaTime;
|
||||
var dampingFactor = Settings.Damping * (float)deltaTime;
|
||||
|
||||
@@ -174,8 +169,6 @@ public class ParticleEmitter : IUpdatableSystem
|
||||
{
|
||||
_lifetimes[i] = Math.Max(0.0f, _lifetimes[i] - (float)deltaTime);
|
||||
|
||||
var t = _lifetimes[i] * lifeTimeInv;
|
||||
|
||||
_velocities[i] += gravityVector;
|
||||
_positions[i] += _velocities[i] * deltaTimeVector;
|
||||
|
||||
@@ -185,8 +178,6 @@ public class ParticleEmitter : IUpdatableSystem
|
||||
|
||||
private void Emit()
|
||||
{
|
||||
// Particle particle = _particles[_particleIndex];
|
||||
|
||||
if (_lifetimes[_particleIndex] > 0) return;
|
||||
|
||||
_positions[_particleIndex] = GetEmitPosition();
|
||||
@@ -194,12 +185,9 @@ public class ParticleEmitter : IUpdatableSystem
|
||||
|
||||
_velocities[_particleIndex] += Vector2.One * Settings.LinearVelocityRandom * ((float)_random.NextDouble() - 0.5f);
|
||||
|
||||
// particle.AngularVelocity = Settings.AngularVelocity;
|
||||
// particle.AngularVelocity += 1f * Settings.AngularVelocityRandom * ((float)_random.NextDouble() - 0.5f);
|
||||
|
||||
_lifetimes[_particleIndex] = Settings.LifeTime;
|
||||
|
||||
_particleIndex = --_particleIndex <= 0 ? _maxParticles - 1 : --_particleIndex;
|
||||
_particleIndex = (_particleIndex + 1) % _maxParticles;
|
||||
}
|
||||
|
||||
private Vector2 GetEmitPosition()
|
||||
@@ -233,7 +221,7 @@ public class ParticleSystem : IUpdatableSystem, IDisposable
|
||||
/// <summary>
|
||||
/// Maximum amount of particles emittable by the system.
|
||||
/// </summary>
|
||||
public int ParticleLimit { get; set; } = 8192;
|
||||
public int ParticleLimit { get; set; } = short.MaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// List of particle emitters created for this ParticleSystem.
|
||||
@@ -245,8 +233,6 @@ public class ParticleSystem : IUpdatableSystem, IDisposable
|
||||
/// </summary>
|
||||
public ParticleSystem()
|
||||
{
|
||||
_particleIndex = ParticleLimit - 1;
|
||||
|
||||
_particlePositions = new Vector2[ParticleLimit];
|
||||
_particleVelocities = new Vector2[ParticleLimit];
|
||||
_particleLifetimes = new float[ParticleLimit];
|
||||
@@ -315,20 +301,14 @@ public class ParticleSystem : IUpdatableSystem, IDisposable
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CleanupParticles();
|
||||
}
|
||||
|
||||
private void CleanupParticles()
|
||||
{
|
||||
Array.Clear(_particlePositions);
|
||||
Array.Clear(_particleVelocities);
|
||||
Array.Clear(_particleLifetimes);
|
||||
}
|
||||
// private Particle[] _particles;
|
||||
|
||||
private Vector2[] _particlePositions, _particleVelocities;
|
||||
private float[] _particleLifetimes;
|
||||
private int _particleIndex;
|
||||
private int _emitterSliceOffset;
|
||||
|
||||
private List<ParticleEmitter> _emitters = new();
|
||||
|
||||
@@ -34,25 +34,33 @@ namespace Voile
|
||||
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; } = 1.0f;
|
||||
public byte R { get; set; }
|
||||
public byte G { get; set; }
|
||||
public byte B { get; set; }
|
||||
public byte A { get; set; } = 255;
|
||||
|
||||
public int Argb
|
||||
{
|
||||
get
|
||||
{
|
||||
int a = (int)Math.Round(A * 255f) << 24;
|
||||
int r = (int)Math.Round(R * 255f) << 16;
|
||||
int g = (int)Math.Round(G * 255f) << 8;
|
||||
int b = (int)Math.Round(B * 255f);
|
||||
int a = A << 24;
|
||||
int r = R << 16;
|
||||
int g = G << 8;
|
||||
int b = B;
|
||||
|
||||
return a | r | g | b;
|
||||
}
|
||||
}
|
||||
|
||||
public Color(float r, float g, float b, float a)
|
||||
public Color(float r, float g, float b, float a = 1.0f)
|
||||
{
|
||||
R = (byte)Math.Clamp(r * 255, 0, 255);
|
||||
G = (byte)Math.Clamp(g * 255, 0, 255);
|
||||
B = (byte)Math.Clamp(b * 255, 0, 255);
|
||||
A = (byte)Math.Clamp(a * 255, 0, 255);
|
||||
}
|
||||
|
||||
public Color(byte r, byte g, byte b, byte a = 255)
|
||||
{
|
||||
R = r;
|
||||
G = g;
|
||||
@@ -60,22 +68,16 @@ namespace Voile
|
||||
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;
|
||||
A = 255; // Default alpha to 255 if not provided
|
||||
B = (byte)(hex & 0xFF);
|
||||
G = (byte)((hex >> 8) & 0xFF);
|
||||
R = (byte)((hex >> 16) & 0xFF);
|
||||
if (hex > 0xFFFFFF) // If the hex value includes alpha
|
||||
{
|
||||
A = (byte)((hex >> 24) & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
public static Color FromHexString(string hex)
|
||||
@@ -104,20 +106,18 @@ namespace Voile
|
||||
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;
|
||||
|
||||
result.R = (byte)Math.Min(255, R + (255 - R) * amount);
|
||||
result.G = (byte)Math.Min(255, G + (255 - G) * amount);
|
||||
result.B = (byte)Math.Min(255, B + (255 - 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);
|
||||
|
||||
result.R = (byte)(R * (1.0f - amount));
|
||||
result.G = (byte)(G * (1.0f - amount));
|
||||
result.B = (byte)(B * (1.0f - amount));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,12 @@ namespace Voile
|
||||
|
||||
public static Color LerpColor(Color colorA, Color colorB, float t)
|
||||
{
|
||||
var r = Lerp(colorA.R, colorB.R, t);
|
||||
var g = Lerp(colorA.G, colorB.G, t);
|
||||
var b = Lerp(colorA.B, colorB.B, t);
|
||||
var a = Lerp(colorA.A, colorB.A, t);
|
||||
t = Math.Clamp(t, 0f, 1f);
|
||||
|
||||
byte r = (byte)(colorA.R + (colorB.R - colorA.R) * t);
|
||||
byte g = (byte)(colorA.G + (colorB.G - colorA.G) * t);
|
||||
byte b = (byte)(colorA.B + (colorB.B - colorA.B) * t);
|
||||
byte a = (byte)(colorA.A + (colorB.A - colorA.A) * t);
|
||||
|
||||
return new Color(r, g, b, a);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user