From 5b7b1f4e4f7b605f8de3fd3d3c0d0e7661fa7f26 Mon Sep 17 00:00:00 2001 From: Lehonti Ramos <17771375+Lehonti@users.noreply.github.com> Date: Sat, 30 Nov 2024 01:06:47 +0100 Subject: [PATCH 1/5] Created `Fraction` type --- Pinta.Core/Algorithms/Mathematics.cs | 13 ++++ Pinta.Core/Classes/DocumentWorkspace.cs | 30 ++++--- Pinta.Core/Classes/Fraction.cs | 60 ++++++++++++++ Pinta.Core/Classes/ScaleFactor.cs | 78 ++++++------------- .../Widgets/Canvas/CanvasRenderer.cs | 23 +++--- 5 files changed, 125 insertions(+), 79 deletions(-) create mode 100644 Pinta.Core/Classes/Fraction.cs diff --git a/Pinta.Core/Algorithms/Mathematics.cs b/Pinta.Core/Algorithms/Mathematics.cs index 48bf1cd25c..c014433023 100644 --- a/Pinta.Core/Algorithms/Mathematics.cs +++ b/Pinta.Core/Algorithms/Mathematics.cs @@ -48,4 +48,17 @@ public static TNumber InvLerp ( TNumber offset = value - from; return offset / valueSpan; } + + public static Fraction Clamp ( + Fraction original, + Fraction min, + Fraction max + ) + where TInt : IBinaryInteger + { + if (min > max) throw new ArgumentException ("Minimum should be less than maximum"); + if (min > original) return min; + if (max < original) return max; + return original; + } } diff --git a/Pinta.Core/Classes/DocumentWorkspace.cs b/Pinta.Core/Classes/DocumentWorkspace.cs index a73a6706a0..54873453ff 100644 --- a/Pinta.Core/Classes/DocumentWorkspace.cs +++ b/Pinta.Core/Classes/DocumentWorkspace.cs @@ -132,8 +132,7 @@ public double Scale { private static Size CoercedToPositive (Size baseSize) => new ( Width: Math.Max (baseSize.Width, 1), - Height: Math.Max (baseSize.Height, 1) - ); + Height: Math.Max (baseSize.Height, 1)); private static Size GetNewViewSize (Size imageSize, double scale) { @@ -229,9 +228,9 @@ public void ScrollCanvas (PointI delta) /// public PointD ViewPointToCanvas (PointD viewPoint) { - var sf = new ScaleFactor (document.ImageSize.Width, ViewSize.Width); - var pt = sf.ScalePoint (viewPoint - Offset); - return new PointD (pt.X, pt.Y); + Fraction sf = ScaleFactor.CreateClamped (document.ImageSize.Width, ViewSize.Width); + PointD pt = sf.ScalePoint (viewPoint - Offset); + return new (pt.X, pt.Y); } /// @@ -239,9 +238,11 @@ public PointD ViewPointToCanvas (PointD viewPoint) /// public PointD CanvasPointToView (PointD canvasPoint) { - var sf = new ScaleFactor (document.ImageSize.Width, ViewSize.Width); - var pt = sf.UnscalePoint (canvasPoint); - return new PointD (pt.X + Offset.X, pt.Y + Offset.Y); + Fraction sf = ScaleFactor.CreateClamped (document.ImageSize.Width, ViewSize.Width); + PointD pt = sf.UnscalePoint (canvasPoint); + return new ( + X: pt.X + Offset.X, + Y: pt.Y + Offset.Y); } public void ZoomIn () @@ -281,20 +282,17 @@ public void ZoomManually () public void ZoomToCanvasRectangle (RectangleD rect) { - double ratio; - - if (document.ImageSize.Width / rect.Width <= document.ImageSize.Height / rect.Height) - ratio = document.ImageSize.Width / rect.Width; - else - ratio = document.ImageSize.Height / rect.Height; + double ratio = + (document.ImageSize.Width / rect.Width <= document.ImageSize.Height / rect.Height) + ? document.ImageSize.Width / rect.Width + : document.ImageSize.Height / rect.Height; PintaCore.Actions.View.ZoomComboBox.ComboBox.GetEntry ().SetText (ViewActions.ToPercent (ratio)); GLib.MainContext.Default ().Iteration (false); //Force update of scrollbar upper before recenter PointD newPoint = new ( X: rect.X + rect.Width / 2, - Y: rect.Y + rect.Height / 2 - ); + Y: rect.Y + rect.Height / 2); RecenterView (newPoint); } diff --git a/Pinta.Core/Classes/Fraction.cs b/Pinta.Core/Classes/Fraction.cs new file mode 100644 index 0000000000..926e40fe37 --- /dev/null +++ b/Pinta.Core/Classes/Fraction.cs @@ -0,0 +1,60 @@ +using System; +using System.Numerics; + +namespace Pinta.Core; + +/// Represents a reduced fraction +/// +/// Only positive values are supported for now. +/// At the time of this writing, support for negative +/// numbers is not needed. +/// +public readonly struct Fraction where TInt : IBinaryInteger +{ + public TInt Numerator { get; } + public TInt Denominator { get; } + public Fraction (TInt numerator, TInt denominator) + { + if (denominator <= TInt.Zero) throw new ArgumentOutOfRangeException (nameof (denominator), "must be greater than 0(denominator = " + denominator + ")"); + if (numerator < TInt.Zero) throw new ArgumentOutOfRangeException (nameof (numerator), "must be greater than 0(numerator = " + numerator + ")"); + if (numerator == TInt.Zero) { + Numerator = TInt.Zero; + Denominator = TInt.One; + } else { + TInt gcd = Mathematics.EuclidGCD (numerator, denominator); + TInt reducedNumerator = numerator / gcd; + TInt reducedDenominator = denominator / gcd; + Numerator = reducedNumerator; + Denominator = reducedDenominator; + } + } + + public static bool operator < (Fraction lhs, Fraction rhs) + => (lhs.Numerator * rhs.Denominator) < (rhs.Numerator * lhs.Denominator); + + public static bool operator > (Fraction lhs, Fraction rhs) + => (lhs.Numerator * rhs.Denominator) > (rhs.Numerator * lhs.Denominator); +} + +public static class FractionExtensions +{ + public static bool LessThan ( + this in Fraction lhs, + TInt rhsNumerator, + TInt rhsDenominator + ) + where TInt : IBinaryInteger + { + return (lhs.Numerator * rhsDenominator) < (lhs.Denominator * rhsNumerator); + } + + public static bool GreaterThan ( + this in Fraction lhs, + TInt rhsNumerator, + TInt rhsDenominator + ) + where TInt : IBinaryInteger + { + return (lhs.Numerator * rhsDenominator) > (lhs.Denominator * rhsNumerator); + } +} diff --git a/Pinta.Core/Classes/ScaleFactor.cs b/Pinta.Core/Classes/ScaleFactor.cs index d4a2141046..688e00c890 100644 --- a/Pinta.Core/Classes/ScaleFactor.cs +++ b/Pinta.Core/Classes/ScaleFactor.cs @@ -7,8 +7,6 @@ // Ported to Pinta by: Jonathan Pobst // ///////////////////////////////////////////////////////////////////////////////// -using System; - namespace Pinta.Core; /// @@ -16,66 +14,40 @@ namespace Pinta.Core; /// Includes methods for Size[F]'s, Point[F]'s, Rectangle[F]'s, /// and various scalars /// -public readonly struct ScaleFactor +public static class ScaleFactor { - private readonly int denominator; - private readonly int numerator; - - public double Ratio { get; } - - public static readonly ScaleFactor OneToOne = new (1, 1); - public static readonly ScaleFactor MinValue = new (1, 100); - public static readonly ScaleFactor MaxValue = new (32, 1); + public static Fraction OneToOne { get; } = new (1, 1); + public static Fraction MinValue { get; } = new (1, 100); + public static Fraction MaxValue { get; } = new (32, 1); - public readonly int ScaleScalar (int x) => - (int) (((long) x * numerator) / denominator); + public static int ScaleScalar (this in Fraction fraction, int x) => + (int) (((long) x * fraction.Numerator) / fraction.Denominator); - public readonly int UnscaleScalar (int x) => - (int) (((long) x * denominator) / numerator); + public static int UnscaleScalar (this in Fraction fraction, int x) => + (int) (((long) x * fraction.Denominator) / fraction.Numerator); - public readonly double ScaleScalar (double x) => - x * numerator / denominator; + public static double ScaleScalar (this in Fraction fraction, double x) => + x * fraction.Numerator / fraction.Denominator; - public readonly double UnscaleScalar (double x) => - x * denominator / numerator; + public static double UnscaleScalar (this in Fraction fraction, double x) => + x * fraction.Denominator / fraction.Numerator; - public readonly PointD ScalePoint (PointD p) => - new (ScaleScalar (p.X), ScaleScalar (p.Y)); + public static PointD ScalePoint (this in Fraction fraction, PointD p) => + new (fraction.ScaleScalar (p.X), fraction.ScaleScalar (p.Y)); - public readonly PointD UnscalePoint (PointD p) => - new (UnscaleScalar (p.X), UnscaleScalar (p.Y)); + public static PointD UnscalePoint (this in Fraction fraction, PointD p) => + new (fraction.UnscaleScalar (p.X), fraction.UnscaleScalar (p.Y)); - public static bool operator < (ScaleFactor lhs, ScaleFactor rhs) => - (lhs.numerator * rhs.denominator) < (rhs.numerator * lhs.denominator); + public static double ComputeRatio (this in Fraction fraction) + => fraction.Numerator / (double) fraction.Denominator; - public static bool operator > (ScaleFactor lhs, ScaleFactor rhs) => - (lhs.numerator * rhs.denominator) > (rhs.numerator * lhs.denominator); - - public ScaleFactor (int numerator, int denominator) + /// + /// Fraction representing the scale factor, + /// clamped to and + /// + public static Fraction CreateClamped (int numerator, int denominator) { - if (denominator <= 0) - throw new ArgumentOutOfRangeException (nameof (denominator), "must be greater than 0(denominator = " + denominator + ")"); - - if (numerator <= 0) - throw new ArgumentOutOfRangeException (nameof (numerator), "must be greater than 0(numerator = " + numerator + ")"); - - // Clamp - if ((numerator * MinValue.denominator) < (MinValue.numerator * denominator)) { - numerator = MinValue.numerator; - denominator = MinValue.denominator; - } else if ((MaxValue.numerator * denominator) > (MaxValue.numerator * denominator)) { - numerator = MaxValue.numerator; - denominator = MaxValue.denominator; - } - - int gcd = Mathematics.EuclidGCD (numerator, denominator); - - int reducedNumerator = numerator / gcd; - int reducedDenominator = denominator / gcd; - - this.numerator = reducedNumerator; - this.denominator = reducedDenominator; - - Ratio = reducedNumerator / (double) reducedDenominator; + Fraction baseFraction = new (numerator, denominator); + return Mathematics.Clamp (baseFraction, MinValue, MaxValue); } } diff --git a/Pinta.Gui.Widgets/Widgets/Canvas/CanvasRenderer.cs b/Pinta.Gui.Widgets/Widgets/Canvas/CanvasRenderer.cs index f5be343a1e..a6c8757d1c 100644 --- a/Pinta.Gui.Widgets/Widgets/Canvas/CanvasRenderer.cs +++ b/Pinta.Gui.Widgets/Widgets/Canvas/CanvasRenderer.cs @@ -23,7 +23,8 @@ public sealed class CanvasRenderer private Size source_size; private Size destination_size; - private ScaleFactor scale_factor; + private Fraction scale_factor; + private double scale_ratio; private ImmutableArray? d_2_s_lookup_x; private ImmutableArray? d_2_s_lookup_y; @@ -49,7 +50,9 @@ public void Initialize (Size sourceSize, Size destinationSize) source_size = sourceSize; destination_size = destinationSize; - scale_factor = new ScaleFactor (source_size.Width, destination_size.Width); + Fraction scaleFactor = ScaleFactor.CreateClamped (source_size.Width, destination_size.Width); + scale_factor = scaleFactor; + scale_ratio = scale_factor.ComputeRatio (); d_2_s_lookup_x = null; d_2_s_lookup_y = null; @@ -66,7 +69,7 @@ public void Render ( // Our rectangle of interest RectangleD r = new RectangleI (offset, dst.GetBounds ().Size).ToDouble (); - bool is_one_to_one = scale_factor.Ratio == 1; + bool is_one_to_one = scale_ratio == 1; using Cairo.Context g = new (dst); @@ -87,14 +90,14 @@ public void Render ( if (!is_one_to_one) { // Scale the source surface based on the zoom level. - double inv_scale = 1.0 / scale_factor.Ratio; + double inv_scale = 1.0 / scale_ratio; g.Scale (inv_scale, inv_scale); } g.Transform (layer.Transform); // Use nearest-neighbor interpolation when zoomed in so that there isn't any smoothing. - ResamplingMode filter = (scale_factor.Ratio <= 1) ? ResamplingMode.NearestNeighbor : ResamplingMode.Bilinear; + ResamplingMode filter = (scale_ratio <= 1) ? ResamplingMode.NearestNeighbor : ResamplingMode.Bilinear; g.SetSourceSurface (surf, filter); @@ -121,7 +124,7 @@ private int GetMinGridLineDistance (ICanvasGridService canvasGrid) int cellWidth = canvasGrid.CellWidth; int minCanvasDistance = Math.Min (cellHeight, cellWidth); - double minRenderedDistance = minCanvasDistance / scale_factor.Ratio; + double minRenderedDistance = minCanvasDistance / scale_ratio; return (int) minRenderedDistance; } @@ -189,7 +192,7 @@ private void RenderPixelGrid (Cairo.ImageSurface dst, PointI offset, ICanvasGrid private static ImmutableArray CreateLookupX ( int srcWidth, int dstWidth, - ScaleFactor scaleFactor) + Fraction scaleFactor) { int length = dstWidth + 1; var lookup = ImmutableArray.CreateBuilder (length); @@ -207,7 +210,7 @@ private static ImmutableArray CreateLookupX ( private static ImmutableArray CreateLookupY ( int srcHeight, int dstHeight, - ScaleFactor scaleFactor) + Fraction scaleFactor) { int length = dstHeight + 1; var lookup = ImmutableArray.CreateBuilder (length); @@ -225,7 +228,7 @@ private static ImmutableArray CreateLookupY ( private static ImmutableArray CreateS2DLookupX ( int srcWidth, int dstWidth, - ScaleFactor scaleFactor) + Fraction scaleFactor) { int length = srcWidth + 1; var lookup = ImmutableArray.CreateBuilder (length); @@ -243,7 +246,7 @@ private static ImmutableArray CreateS2DLookupX ( private static ImmutableArray CreateS2DLookupY ( int srcHeight, int dstHeight, - ScaleFactor scaleFactor) + Fraction scaleFactor) { int length = srcHeight + 1; var lookup = ImmutableArray.CreateBuilder (length); From e371a9130db1c1cbb5bc4ff35032fbeb0c62f36d Mon Sep 17 00:00:00 2001 From: Lehonti Ramos <17771375+Lehonti@users.noreply.github.com> Date: Sat, 30 Nov 2024 01:28:24 +0100 Subject: [PATCH 2/5] Added test cases --- tests/Pinta.Core.Tests/MathematicsTests.cs | 58 ++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tests/Pinta.Core.Tests/MathematicsTests.cs b/tests/Pinta.Core.Tests/MathematicsTests.cs index c58320c02f..35bafb647a 100644 --- a/tests/Pinta.Core.Tests/MathematicsTests.cs +++ b/tests/Pinta.Core.Tests/MathematicsTests.cs @@ -66,6 +66,27 @@ public void InvLerp_RejectsSame (LerpCase values) Assert.Throws (() => Mathematics.InvLerp (values.from, values.to, values.result)); } + [TestCaseSource (nameof (unequal_fraction_cases))] + public void Fraction_LessThan (UnequalFractionCase fractions) + { + Assert.That (fractions.lesser < fractions.greater, "Comparison failed"); + Assert.That (!(fractions.lesser > fractions.greater), "Negated comparison failed"); + } + + [TestCaseSource (nameof (unequal_fraction_cases))] + public void Fraction_GreaterThan (UnequalFractionCase fractions) + { + Assert.That (fractions.greater > fractions.lesser, "Comparison failed"); + Assert.That (!(fractions.greater < fractions.lesser), "Negated comparison failed"); + } + + [TestCaseSource (nameof (equal_fraction_cases))] + public void Fraction_Equal_Reduction (EqualFractionCase fractions) + { + Assert.That (fractions.lhs.Numerator == fractions.rhs.Numerator, "Numerators not equal"); + Assert.That (fractions.lhs.Denominator == fractions.rhs.Denominator, "Denominators not equal"); + } + private static readonly IReadOnlyList lerp_cases = GenerateRegularLerpCases () .Concat (GenerateOneWayLerpCases ()) @@ -82,6 +103,43 @@ public void InvLerp_RejectsSame (LerpCase values) .Select (c => new TestCaseData (c)) .ToArray (); + public readonly record struct EqualFractionCase ( + Fraction lhs, + Fraction rhs); + + private static readonly IReadOnlyList equal_fraction_cases = + GenerateEqualFractions () + .Select (c => new TestCaseData (c)) + .ToArray (); + + private static IEnumerable GenerateEqualFractions () + { + yield return new (lesser: new (1, 3), greater: new (2, 6)); + yield return new (lesser: new (1, 2), greater: new (2, 4)); + yield return new (lesser: new (1, 1), greater: new (1, 1)); + yield return new (lesser: new (1, 1), greater: new (2, 2)); + yield return new (lesser: new (2, 3), greater: new (4, 6)); + } + + public readonly record struct UnequalFractionCase ( + Fraction lesser, + Fraction greater); + + private static readonly IReadOnlyList unequal_fraction_cases = + GenerateUnequalFractions () + .Select (c => new TestCaseData (c)) + .ToArray (); + + private static IEnumerable GenerateUnequalFractions () + { + yield return new (lesser: new (1, 2), greater: new (1, 1)); + yield return new (lesser: new (1, 1), greater: new (2, 1)); + yield return new (lesser: new (2, 3), greater: new (2, 1)); + yield return new (lesser: new (3, 2), greater: new (2, 1)); + yield return new (lesser: new (2, 1), greater: new (5, 2)); + yield return new (lesser: new (5, 2), greater: new (3, 1)); + } + public readonly record struct LerpCase ( double from, double to, From e5c8efe4fb94364e25a489d14d163937fac3f056 Mon Sep 17 00:00:00 2001 From: Lehonti Ramos <17771375+Lehonti@users.noreply.github.com> Date: Sat, 30 Nov 2024 01:31:37 +0100 Subject: [PATCH 3/5] Corrected test cases --- tests/Pinta.Core.Tests/MathematicsTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Pinta.Core.Tests/MathematicsTests.cs b/tests/Pinta.Core.Tests/MathematicsTests.cs index 35bafb647a..9f8a3833b4 100644 --- a/tests/Pinta.Core.Tests/MathematicsTests.cs +++ b/tests/Pinta.Core.Tests/MathematicsTests.cs @@ -112,13 +112,13 @@ public readonly record struct EqualFractionCase ( .Select (c => new TestCaseData (c)) .ToArray (); - private static IEnumerable GenerateEqualFractions () + private static IEnumerable GenerateEqualFractions () { - yield return new (lesser: new (1, 3), greater: new (2, 6)); - yield return new (lesser: new (1, 2), greater: new (2, 4)); - yield return new (lesser: new (1, 1), greater: new (1, 1)); - yield return new (lesser: new (1, 1), greater: new (2, 2)); - yield return new (lesser: new (2, 3), greater: new (4, 6)); + yield return new (lhs: new (1, 3), rhs: new (2, 6)); + yield return new (lhs: new (1, 2), rhs: new (2, 4)); + yield return new (lhs: new (1, 1), rhs: new (1, 1)); + yield return new (lhs: new (1, 1), rhs: new (2, 2)); + yield return new (lhs: new (2, 3), rhs: new (4, 6)); } public readonly record struct UnequalFractionCase ( From b82eacae5cdc422e3ebfbb765f180d48d6a90fb5 Mon Sep 17 00:00:00 2001 From: Lehonti Ramos <17771375+Lehonti@users.noreply.github.com> Date: Sat, 30 Nov 2024 01:51:43 +0100 Subject: [PATCH 4/5] Added equality comparison operators --- Pinta.Core/Classes/Fraction.cs | 18 ++++++++++++++++++ tests/Pinta.Core.Tests/MathematicsTests.cs | 15 +++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/Pinta.Core/Classes/Fraction.cs b/Pinta.Core/Classes/Fraction.cs index 926e40fe37..64e8f274ab 100644 --- a/Pinta.Core/Classes/Fraction.cs +++ b/Pinta.Core/Classes/Fraction.cs @@ -29,11 +29,29 @@ public Fraction (TInt numerator, TInt denominator) } } + public static bool operator == (Fraction lhs, Fraction rhs) + => lhs.Equals (rhs); + + public static bool operator != (Fraction lhs, Fraction rhs) + => !lhs.Equals (rhs); + public static bool operator < (Fraction lhs, Fraction rhs) => (lhs.Numerator * rhs.Denominator) < (rhs.Numerator * lhs.Denominator); public static bool operator > (Fraction lhs, Fraction rhs) => (lhs.Numerator * rhs.Denominator) > (rhs.Numerator * lhs.Denominator); + + public bool Equals (Fraction other) + => Numerator == other.Numerator && Denominator == other.Denominator; + + public override bool Equals (object? obj) + { + if (obj is not Fraction other) return false; + return Equals (other); + } + + public override int GetHashCode () + => Numerator.GetHashCode () ^ Denominator.GetHashCode (); } public static class FractionExtensions diff --git a/tests/Pinta.Core.Tests/MathematicsTests.cs b/tests/Pinta.Core.Tests/MathematicsTests.cs index 9f8a3833b4..d05d2a7668 100644 --- a/tests/Pinta.Core.Tests/MathematicsTests.cs +++ b/tests/Pinta.Core.Tests/MathematicsTests.cs @@ -80,6 +80,13 @@ public void Fraction_GreaterThan (UnequalFractionCase fractions) Assert.That (!(fractions.greater < fractions.lesser), "Negated comparison failed"); } + [TestCaseSource (nameof (unequal_fraction_cases))] + public void Fraction_Unequal (UnequalFractionCase fractions) + { + Assert.That (fractions.lesser != fractions.greater, "Inequality operator comparison failed"); + Assert.That (!(fractions.lesser == fractions.greater), "Equality operator comparison failed"); + } + [TestCaseSource (nameof (equal_fraction_cases))] public void Fraction_Equal_Reduction (EqualFractionCase fractions) { @@ -87,6 +94,14 @@ public void Fraction_Equal_Reduction (EqualFractionCase fractions) Assert.That (fractions.lhs.Denominator == fractions.rhs.Denominator, "Denominators not equal"); } + [TestCaseSource (nameof (equal_fraction_cases))] + public void Fraction_Equal (EqualFractionCase fractions) + { + Assert.That (fractions.lhs == fractions.rhs, "Operator comparison failed"); + Assert.That (!(fractions.lhs != fractions.rhs), "Inequality operator comparison failed"); + Assert.That (fractions.lhs.GetHashCode () == fractions.rhs.GetHashCode (), "Hashes are not equal"); + } + private static readonly IReadOnlyList lerp_cases = GenerateRegularLerpCases () .Concat (GenerateOneWayLerpCases ()) From 80f1721cfa2e5770328ef29d01feab265932c8c7 Mon Sep 17 00:00:00 2001 From: Lehonti Ramos <17771375+Lehonti@users.noreply.github.com> Date: Sat, 30 Nov 2024 01:54:40 +0100 Subject: [PATCH 5/5] Added case rejecting negative denominator --- tests/Pinta.Core.Tests/MathematicsTests.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/Pinta.Core.Tests/MathematicsTests.cs b/tests/Pinta.Core.Tests/MathematicsTests.cs index d05d2a7668..002dbbd67c 100644 --- a/tests/Pinta.Core.Tests/MathematicsTests.cs +++ b/tests/Pinta.Core.Tests/MathematicsTests.cs @@ -102,6 +102,12 @@ public void Fraction_Equal (EqualFractionCase fractions) Assert.That (fractions.lhs.GetHashCode () == fractions.rhs.GetHashCode (), "Hashes are not equal"); } + [TestCase] + public void Fraction_Rejects_Zero_Denominator () + { + Assert.Throws (() => new Fraction (1, 0)); + } + private static readonly IReadOnlyList lerp_cases = GenerateRegularLerpCases () .Concat (GenerateOneWayLerpCases ())