Skip to content

Commit

Permalink
Rework EffectCombiner into ECS (#291)
Browse files Browse the repository at this point in the history
* zzre: Add EffectCombiner resource

* Starting shader

* Add initial effect material, deprecating the legacy one

* zzre: Rework DynamicMesh again

* zzre: Allow custom indices in DynamicMesh

* Remove obsolete IRenderable interface

* Unbreak models and scene changes
How have I missed that?

* Rename InstanceRange to InstanceArena

* Sketch out more of an ECS effect combiner

* MovingPlanes and EffectRenderer that at least compiles

* Non-working EffectEditor uses ECS effect combiner

* zzre: Fix so far as to see some moving planes

* Fix View billboarding

* Validate textures before returning from cache

* Playback, resetting and MovingPlanes not rendering with minProgress

* Improve interface a bit and fix MovingPlanes scaling into the negative

* Add RandomPlanes

* Fix looping behaviour and set better background color

* Add all effect parts to EffectCombiner and ECSExplorer

* Spawn effect on door unlocking

* DeepSource had some good suggestions
  • Loading branch information
Helco authored Feb 1, 2024
1 parent e5f31b7 commit a8877d9
Show file tree
Hide file tree
Showing 59 changed files with 1,869 additions and 464 deletions.
32 changes: 32 additions & 0 deletions zzio/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,36 @@ public static IEnumerable<TElement> SelectMany<TElement>(this IEnumerable<IEnume

public static TCompare? MaxOrDefault<TElement, TCompare>(this IEnumerable<TElement> set, Func<TElement, TCompare> selector, TCompare? defaultValue = default) =>
set.Any() ? set.Max(selector) : defaultValue;

public delegate bool ReferencePredicate<TElement>(in TElement element);

public static int Count<TElement>(this ReadOnlySpan<TElement> span, ReferencePredicate<TElement> predicate)
{
int count = 0;
foreach (ref readonly var element in span)
{
if (predicate(in element))
count++;
}
return count;
}

public static int Count<TElement>(this Span<TElement> span, ReferencePredicate<TElement> predicate)
{
int count = 0;
foreach (ref readonly var element in span)
{
if (predicate(in element))
count++;
}
return count;
}

public static Range Sub(this Range full, Range sub, int maxValue = int.MaxValue)
{
var (fullOffset, fullLength) = full.GetOffsetAndLength(maxValue);
var (subOffset, subLength) = sub.GetOffsetAndLength(fullLength);
int newOffset = fullOffset + subOffset;
return newOffset..(newOffset + subLength);
}
}
2 changes: 2 additions & 0 deletions zzio/primitives/FColor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public void Write(BinaryWriter w)

public static FColor operator *(FColor a, FColor b) => new(a.r * b.r, a.g * b.g, a.b * b.b, a.a * b.a);

public static FColor operator *(FColor a, float f) => new(a.r * f, a.g * f, a.b * f, a.a * f);

public static implicit operator IColor(FColor c) => new(
(byte)(c.r * 255f),
(byte)(c.g * 255f),
Expand Down
47 changes: 47 additions & 0 deletions zzre.core.tests/TestRangeCollection.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using NUnit.Framework;
using NUnit.Framework.Constraints;

namespace zzre.tests;

Expand Down Expand Up @@ -39,6 +40,18 @@ public void AddMergeOverlapping()
Assert.AreEqual(new[] { 3..10 }, coll);
}

[Test]
public void AddMergeExactFit()
{
var coll = new RangeCollection
{
0..3,
7..10,
3..7
};
Assert.AreEqual(new[] { 0..10 }, coll);
}

[Test]
public void AddMergeComplex()
{
Expand All @@ -53,6 +66,40 @@ public void AddMergeComplex()
Assert.AreEqual(new[] { 0..17 }, coll);
}

[Test]
public void AddBestFit()
{
// Finds hole in the middle
var coll = new RangeCollection { 0..3, 7..10 };
Assert.AreEqual(3..5, coll.AddBestFit(2));
Assert.AreEqual(new[] { 0..5, 7..10 }, coll);

// Finds hole at the start
coll = new RangeCollection { 7..10 };
Assert.AreEqual(0..3, coll.AddBestFit(3));
Assert.AreEqual(new[] { 0..3, 7..10 }, coll);

// Ignores holes that are too small
coll = new RangeCollection { 2..5, 7..10, 15..20 };
Assert.AreEqual(10..14, coll.AddBestFit(4));
Assert.AreEqual(new[] { 2..5, 7..14, 15..20 }, coll);

// Preferes better fitting holes
coll = new RangeCollection { 5..10, 13..15 };
Assert.AreEqual(10..12, coll.AddBestFit(2));
Assert.AreEqual(new[] { 5..12, 13..15 }, coll);

// Returns null on empty and too small
coll = new RangeCollection(5);
Assert.IsNull(coll.AddBestFit(10));
Assert.IsEmpty(coll);

// Returns null on too small
coll = new RangeCollection(10) { 3..8 };
Assert.IsNull(coll.AddBestFit(9));
Assert.AreEqual(new[] { 3..8 }, coll);
}

[Test]
public void RemoveNonExistant()
{
Expand Down
15 changes: 11 additions & 4 deletions zzre.core/GameTime.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;

namespace zzre;

public class GameTime
{
private readonly Stopwatch watch = new();
private readonly List<double> curFrametimes = new List<double>(60);

private int curFPS = 0;
private TimeSpan lastSecond;
private TimeSpan frameStart;

Expand All @@ -18,9 +20,12 @@ public class GameTime
public float Delta => Math.Min(MaxDelta, UnclampedDelta);
public float UnclampedDelta { get; private set; } = 0.0f;
public int Framerate { get; private set; } = 0;
public double FrametimeAvg { get; private set; } = 0f;
public double FrametimeSD { get; private set; } = 0f;
public bool HasFramerateChanged { get; private set; } = false;

private TimeSpan TargetFrametime => TimeSpan.FromSeconds(1.0 / TargetFramerate);
public string FormattedStats => $"FPS: {Framerate} | FT: {FrametimeAvg:F2}ms";

public GameTime()
{
Expand All @@ -33,19 +38,21 @@ public void BeginFrame()
UnclampedDelta = (float)(watch.Elapsed - frameStart).TotalSeconds;
frameStart = watch.Elapsed;

curFPS++;
HasFramerateChanged = false;
if ((frameStart - lastSecond).TotalSeconds >= 1)
{
Framerate = (int)(curFPS / (frameStart - lastSecond).TotalSeconds + 0.5);
Framerate = (int)(curFrametimes.Count / (frameStart - lastSecond).TotalSeconds + 0.5);
FrametimeAvg = curFrametimes.Average();
FrametimeSD = curFrametimes.Sum(f => Math.Pow(f - FrametimeAvg, 2)) / curFrametimes.Count;
curFrametimes.Clear();
lastSecond = frameStart;
curFPS = 0;
HasFramerateChanged = true;
}
}

public void EndFrame()
{
curFrametimes.Add((watch.Elapsed - frameStart).TotalMilliseconds);
int delayMs = (int)(TargetFrametime - (watch.Elapsed - frameStart)).TotalMilliseconds;
if (delayMs > 0)
Thread.Sleep(delayMs);
Expand Down
75 changes: 74 additions & 1 deletion zzre.core/RangeCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using zzio;

namespace zzre;

Expand All @@ -17,6 +18,14 @@ public class RangeCollection : ICollection<Range>, IReadOnlyCollection<Range>
: new Range(Index.Start, Index.Start);
public int Area => ranges.Sum(r => r.GetOffsetAndLength(MaxRangeValue).Length);

public int MinValue => ranges.Any()
? ranges.First().Start.GetOffset(MaxRangeValue)
: -1;

public int MaxValue => ranges.Any()
? ranges.Last().End.GetOffset(MaxRangeValue) - 1
: -1;

private int _maxRangeValue;
public int MaxRangeValue
{
Expand All @@ -35,6 +44,13 @@ public RangeCollection(int maxRangeValue = int.MaxValue) =>
public void Add(Range item)
{
var (itemOffset, itemLength) = item.GetOffsetAndLength(MaxRangeValue);
if (itemOffset == 0 && itemLength >= MaxRangeValue)
{
ranges.Clear();
ranges.Add(..MaxRangeValue);
return;
}

var nearItem = new Range(
Math.Max(0, itemOffset - 1),
itemOffset + itemLength + (itemOffset + itemLength < MaxRangeValue ? 1 : 0));
Expand All @@ -53,9 +69,16 @@ public void Add(Range item)

public bool Remove(Range remove)
{
var intersections = FindIntersections(remove).ToArray();
var removeStart = remove.Start.GetOffset(MaxRangeValue);
var removeEnd = remove.End.GetOffset(MaxRangeValue);
if (removeStart == 0 && removeEnd == MaxRangeValue)
{
var wasNotEmpty = ranges.Any();
Clear();
return wasNotEmpty;
}

var intersections = FindIntersections(remove).ToArray();
var result = false;
foreach (var i in intersections)
{
Expand All @@ -70,6 +93,56 @@ public bool Remove(Range remove)
return result;
}

public Range? AddBestFit(int length)
{
if (!ranges.Any())
{
if (MaxRangeValue < length)
return null;
Add(0..length);
return 0..length;
}

var lastEnd = 0;
int bestStart = -1;
int bestLength = int.MaxValue;
foreach (var curRange in ranges)
{
var (curStart, curEnd) = curRange.GetOffsetAndLength(MaxRangeValue);
curEnd += curStart;

int curHoleLength = curStart - lastEnd;
if (curHoleLength >= length && curHoleLength < bestLength)
{
bestStart = lastEnd;
bestLength = curHoleLength;
}
if (bestLength == length)
break; // it does not get better than optimal

lastEnd = curEnd;
}
if (bestStart < 0)
return null;
var newRange = bestStart..(bestStart + length);
Add(newRange);
return newRange;
}

public Range? RemoveBestFit(int length)
{
var range = ranges
.Where(r => r.GetLength(MaxRangeValue) >= length)
.OrderBy(r => r.GetLength(MaxRangeValue))
.FirstOrDefault();
if (range.Equals(default))
return null;
ranges.Remove(range);
range = range.Start..range.Start.Offset(length);
ranges.Add(range);
return range;
}

public bool Contains(Range item) =>
FindIntersections(item)
.Any(i => Contains(item, i));
Expand Down
3 changes: 3 additions & 0 deletions zzre.core/math/NumericsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ public static Vector3 OnSphere(this Random random)

public static int NextSign(this Random random) => random.Next(2) * 2 - 1;

public static uint Next(this Random random, uint exclusiveMax) =>
checked((uint)random.Next((int)exclusiveMax));

public static float Next(this Random random, float min, float max) =>
min + random.NextFloat() * (max - min);

Expand Down
Loading

0 comments on commit a8877d9

Please sign in to comment.