Graphing/Base/Graphables/IntegralEquation.cs

240 lines
7.7 KiB
C#

using Graphing.Abstract;
using Graphing.Forms;
using Graphing.Parts;
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
namespace Graphing.Graphables;
public class IntegralEquation : Graphable, IIntegrable, IDerivable
{
protected readonly Equation? baseEqu;
protected readonly EquationDelegate? baseEquDel;
protected readonly IntegralEquation? altBaseEqu;
protected readonly bool usingAlt;
public IntegralEquation(Equation baseEquation)
{
string oldName = baseEquation.Name, newName;
if (oldName.StartsWith("Integral of ")) newName = "Second Integral of " + oldName[12..];
else if (oldName.StartsWith("Second Integral of ")) newName = "Third Integral of " + oldName[19..];
else newName = "Integral of " + oldName;
Name = newName;
baseEqu = baseEquation;
baseEquDel = baseEquation.GetDelegate();
altBaseEqu = null;
usingAlt = false;
}
public IntegralEquation(IntegralEquation baseEquation)
{
string oldName = baseEquation.Name, newName;
if (oldName.StartsWith("Integral of ")) newName = "Second Integral of " + oldName[12..];
else if (oldName.StartsWith("Second Integral of ")) newName = "Third Integral of " + oldName[19..];
else newName = "Integral of " + oldName;
Name = newName;
baseEqu = null;
baseEquDel = null;
altBaseEqu = baseEquation;
usingAlt = true;
}
public override Graphable ShallowCopy() => new IntegralEquation(this);
public override IEnumerable<IGraphPart> GetItemsToRender(in GraphForm graph)
{
const int step = 10;
double epsilon = Math.Abs(graph.ScreenSpaceToGraphSpace(new Int2(0, 0)).x
- graph.ScreenSpaceToGraphSpace(new Int2(step / 2, 0)).x) / 5;
epsilon *= graph.DpiFloat / 192;
List<IGraphPart> lines = [];
Int2 originLocation = graph.GraphSpaceToScreenSpace(new Float2(0, 0));
if (originLocation.x < 0)
{
// Origin is off the left side of the screen.
// Get to the left side from the origin.
double start = graph.MinVisibleGraph.x, end = graph.MaxVisibleGraph.x;
SetInternalStepper(start, epsilon);
// Now we can start.
double previousX = stepX;
double previousY = stepY;
for (double x = start; x <= end; x += epsilon)
{
MoveInternalStepper(epsilon);
lines.Add(new GraphLine(new Float2(previousX, previousY),
new Float2(stepX, stepY)));
previousX = stepX;
previousY = stepY;
}
}
else if (originLocation.x > graph.ClientRectangle.Width)
{
// Origin is off the right side of the screen.
// Get to the right side of the origin.
double start = graph.MaxVisibleGraph.x, end = graph.MinVisibleGraph.x;
SetInternalStepper(start, epsilon);
// Now we can start.
double previousX = stepX;
double previousY = stepY;
for (double x = start; x >= end; x -= epsilon)
{
MoveInternalStepper(-epsilon);
lines.Add(new GraphLine(new Float2(previousX, previousY),
new Float2(stepX, stepY)));
previousX = stepX;
previousY = stepY;
}
}
else
{
// Origin is on-screen.
// We need to do two cycles.
// Start with right.
double start = 0, end = graph.MaxVisibleGraph.x;
SetInternalStepper(start, epsilon);
double previousX = stepX;
double previousY = stepY;
for (double x = start; x <= end; x += epsilon)
{
MoveInternalStepper(epsilon);
lines.Add(new GraphLine(new Float2(previousX, previousY),
new Float2(stepX, stepY)));
previousX = stepX;
previousY = stepY;
}
// Now do left.
start = 0;
end = graph.MinVisibleGraph.x;
SetInternalStepper(start, epsilon);
previousX = stepX;
previousY = stepY;
for (double x = start; x >= end; x -= epsilon)
{
MoveInternalStepper(-epsilon);
lines.Add(new GraphLine(new Float2(previousX, previousY),
new Float2(stepX, stepY)));
previousX = stepX;
previousY = stepY;
}
}
return lines;
}
private double stepX = 0;
private double stepY = 0;
private void SetInternalStepper(double x, double dX)
{
stepX = 0;
stepY = 0;
if (usingAlt) altBaseEqu!.SetInternalStepper(0, dX);
if (x > 0)
{
while (stepX < x) MoveInternalStepper(dX);
}
else if (x < 0)
{
while (x < stepX) MoveInternalStepper(-dX);
}
}
private void MoveInternalStepper(double dX)
{
stepX += dX;
if (usingAlt)
{
altBaseEqu!.MoveInternalStepper(dX);
stepY += altBaseEqu!.stepY * dX;
}
else
{
stepY += (baseEquDel!(stepX - baseEqu!.OffsetX) + baseEqu.OffsetY) * dX;
}
}
// Try to avoid using this, as it converts the integral into a
// far less efficient format (uses the `IntegralAtPoint` method).
public Equation AsEquation() => new(IntegralAtPoint)
{
Name = Name,
Color = Color
};
public Graphable Derive()
{
if (usingAlt) return altBaseEqu!.ShallowCopy();
else return (Equation)baseEqu!.ShallowCopy();
}
public Graphable Integrate() => new IntegralEquation(this);
// Standard integral method.
// Inefficient for successive calls.
public double IntegralAtPoint(double x)
{
if (x > 0)
{
double start = Math.Min(0, x), end = Math.Max(0, x);
const double step = 1e-3;
double sum = 0;
SetInternalStepper(start, step);
for (double t = start; t <= end; t += step)
{
MoveInternalStepper(step);
sum += stepY * step;
}
return sum;
}
else if (x < 0)
{
double start = Math.Max(0, x), end = Math.Min(0, x);
const double step = 1e-3;
double sum = 0;
SetInternalStepper(start, step);
for (double t = start; t >= end; t -= step)
{
MoveInternalStepper(-step);
sum -= stepY * step;
}
return sum;
}
else return 0;
}
public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor)
{
Int2 screenMousePos = graph.GraphSpaceToScreenSpace(graphMousePos);
Int2 screenPos = graph.GraphSpaceToScreenSpace(new Float2(graphMousePos.x,
IntegralAtPoint(graphMousePos.x)));
double allowedDist = factor * graph.DpiFloat * 80 / 192;
Int2 dist = new(screenPos.x - screenMousePos.x,
screenPos.y - screenMousePos.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) =>
new(graphMousePos.x, IntegralAtPoint(graphMousePos.x));
}