diff --git a/Base/Graphables/ColumnTable.cs b/Base/Graphables/ColumnTable.cs index 4038ca6..b438b62 100644 --- a/Base/Graphables/ColumnTable.cs +++ b/Base/Graphables/ColumnTable.cs @@ -48,9 +48,6 @@ public class ColumnTable : Graphable return items; } - public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor) => false; - public override Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) => default; - // Nothing to preload, everything is already cached. public override void Preload(Float2 xRange, Float2 yRange, double step) { } } diff --git a/Base/Graphables/SlopeField.cs b/Base/Graphables/SlopeField.cs index b96d64e..858d2a9 100644 --- a/Base/Graphables/SlopeField.cs +++ b/Base/Graphables/SlopeField.cs @@ -74,9 +74,46 @@ public class SlopeField : Graphable public override void EraseCache() => cache.Clear(); public override long GetCacheBytes() => cache.Count * 48; + + public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor) + { + Float2 nearestPos = new(Math.Round(graphMousePos.x * detail) / detail, + Math.Round(graphMousePos.y * detail) / detail); - public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor) => false; - public override Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) => default; + double epsilon = 1 / (detail * 2.0); + GraphLine line = GetFromCache(epsilon, nearestPos.x, nearestPos.y); + double slope = (line.b.y - line.a.y) / (line.b.x - line.a.x); + + if (graphMousePos.x < Math.Min(line.a.x, line.b.x) || + graphMousePos.x > Math.Max(line.a.x, line.b.x)) return false; + + double allowedDist = factor * graph.DpiFloat * 10 / 192; + + double lineX = graphMousePos.x, + lineY = slope * (lineX - nearestPos.x) + nearestPos.y; + + Int2 pointScreen = graph.GraphSpaceToScreenSpace(new Float2(lineX, lineY)); + Int2 mouseScreen = graph.GraphSpaceToScreenSpace(graphMousePos); + Int2 dist = new(pointScreen.x - mouseScreen.x, + pointScreen.y - mouseScreen.y); + double totalDist = Math.Sqrt(dist.x * dist.x + dist.y * dist.y); + return totalDist <= allowedDist; + } + public override Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) + { + Float2 nearestPos = new(Math.Round(graphMousePos.x * detail) / detail, + Math.Round(graphMousePos.y * detail) / detail); + + double epsilon = 1 / (detail * 2.0); + GraphLine line = GetFromCache(epsilon, nearestPos.x, nearestPos.y); + double slope = (line.b.y - line.a.y) / (line.b.x - line.a.x); + + double lineX = graphMousePos.x, + lineY = slope * (lineX - nearestPos.x) + nearestPos.y; + Float2 point = new(lineX, lineY); + + return point; + } public override void Preload(Float2 xRange, Float2 yRange, double step) { diff --git a/Base/Graphables/TangentLine.cs b/Base/Graphables/TangentLine.cs index 549c881..f32c460 100644 --- a/Base/Graphables/TangentLine.cs +++ b/Base/Graphables/TangentLine.cs @@ -21,10 +21,12 @@ public class TangentLine : Graphable protected readonly double length; - protected double currentSlope; + // X is slope, Y is height. + protected Float2 currentSlope; // No binary search for this, I want it to be exact. - protected Dictionary slopeCache; + // Value: X is slope, Y is height. + protected Dictionary slopeCache; public TangentLine(double length, double position, Equation parent) { @@ -39,27 +41,28 @@ public class TangentLine : Graphable public override IEnumerable GetItemsToRender(in GraphForm graph) { - Float2 point = new(Position, parentEqu(Position)); - return [MakeSlopeLine(point, currentSlope), - new GraphUiCircle(point, 8)]; + Float2 point = new(Position, currentSlope.y); + return [MakeSlopeLine(), new GraphUiCircle(point, 8)]; } - protected GraphLine MakeSlopeLine(Float2 position, double slope) + protected GraphLine MakeSlopeLine() { - double dirX = length, dirY = slope * length; + double dirX = length, dirY = currentSlope.x * length; double magnitude = Math.Sqrt(dirX * dirX + dirY * dirY); dirX /= magnitude * 2 / length; dirY /= magnitude * 2 / length; - return new(new(position.x + dirX, position.y + dirY), new(position.x - dirX, position.y - dirY)); + return new(new(Position + dirX, currentSlope.y + dirY), new(Position - dirX, currentSlope.y - dirY)); } - protected double DerivativeAtPoint(double x) + protected Float2 DerivativeAtPoint(double x) { // If value is already computed, return it. - if (slopeCache.TryGetValue(x, out double y)) return y; + if (slopeCache.TryGetValue(x, out Float2 val)) return val; const double step = 1e-3; - double result = (parentEqu(x + step) - parentEqu(x)) / step; + + double initial = parentEqu(x); + Float2 result = new((parentEqu(x + step) - initial) / step, initial); slopeCache.Add(x, result); return result; } @@ -67,10 +70,37 @@ public class TangentLine : Graphable public override Graphable DeepCopy() => new TangentLine(length, Position, parent); public override void EraseCache() => slopeCache.Clear(); - public override long GetCacheBytes() => slopeCache.Count * 16; + public override long GetCacheBytes() => slopeCache.Count * 24; - public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor) => false; - public override Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) => default; + public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor) + { + GraphLine line = MakeSlopeLine(); + + if (graphMousePos.x < Math.Min(line.a.x - 0.25, line.b.x - 0.25) || + graphMousePos.x > Math.Max(line.a.x + 0.25, line.b.x + 0.25)) return false; + + double allowedDist = factor * graph.DpiFloat * 80 / 192; + + double lineX = graphMousePos.x, + lineY = currentSlope.x * (lineX - Position) + currentSlope.y; + + Int2 pointScreen = graph.GraphSpaceToScreenSpace(new Float2(lineX, lineY)); + Int2 mouseScreen = graph.GraphSpaceToScreenSpace(graphMousePos); + Int2 dist = new(pointScreen.x - mouseScreen.x, + pointScreen.y - mouseScreen.y); + double totalDist = Math.Sqrt(dist.x * dist.x + dist.y * dist.y); + return totalDist <= allowedDist; + } + public override Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) + { + GraphLine line = MakeSlopeLine(); + + double lineX = Math.Clamp(graphMousePos.x, + Math.Min(line.a.x, line.b.x), + Math.Max(line.a.x, line.b.x)), + lineY = currentSlope.x * (lineX - Position) + currentSlope.y; + return new Float2(lineX, lineY); + } public override void Preload(Float2 xRange, Float2 yRange, double step) { diff --git a/Testing/Program.cs b/Testing/Program.cs index 090be95..88beefd 100644 --- a/Testing/Program.cs +++ b/Testing/Program.cs @@ -14,8 +14,8 @@ internal static class Program GraphForm graph = new("One Of The Graphing Calculators Of All Time"); - Equation possibleA = new(x => Math.Sin(x)); - SlopeField sf = new(2, (x, y) => Math.Cos(x)); + Equation possibleA = new(x => x * x * x); + SlopeField sf = new(2, (x, y) => 1 / x); TangentLine tl = new(2, 2, possibleA); graph.Graph(possibleA, sf, tl);