public CalculateMinMax(Calculator calc, IEnumerable<Expression> criteria, bool isMax) { InitializeComponent(); _calc = calc; string operationType = isMax ? "maximum" : "minimum"; Text = string.Format(Text, operationType); instructions.Text = string.Format(instructions.Text, operationType); var buttons = from expr in criteria select new RadioButton { Text = expr.ToString(), Tag = expr, AutoSize = true }; _checkables = buttons.ToArray(); if (_checkables.Length == 0) { throw new ArgumentException("Must contain at least one expression.", "criteria"); } _checkables[0].Checked = true; flowLayoutPanel1.Controls.AddRange(_checkables); OnManualBoundsRadioCheckedChanged(null, null); }
/// <summary> /// Calculates the area between, in the case of this overload, two curves. /// </summary> /// <param name="calc">The <see cref="Calculator"/> to calculate the Y-values with.</param> /// <param name="top">The curve that defines the top of the integral.</param> /// <param name="bottom">The curve that defines the bottom of the integral.</param> /// <param name="leftBound">The left bound of the integral.</param> /// <param name="rightBound">The right bound of the integral.</param> /// <returns> /// The area between the curves <paramref name="top"/> and <paramref name="bottom"/>. /// </returns> public static double CalculateIntegral(Calculator calc, StandardExpression top, StandardExpression bottom, double leftBound, double rightBound) { Func<Calculator, double> distanceMethod; // Optimize for anything that might be a constant here if (top.Expression.IsConstant && bottom.Expression.IsConstant) { // The integral between two constants is just a rectangle double topConstant = calc.Evaluate(top.Expression); double bottomConstant = calc.Evaluate(bottom.Expression); // Area = Width * Height return (rightBound - leftBound) * (topConstant - bottomConstant); } if (top.Expression.IsConstant) { double topConstant = calc.Evaluate(top.Expression); distanceMethod = c => topConstant - c.Evaluate(bottom.Expression); } else if (bottom.Expression.IsConstant) { double bottomConstant = calc.Evaluate(bottom.Expression); distanceMethod = c => c.Evaluate(top.Expression) - bottomConstant; } else { distanceMethod = c => c.Evaluate(top.Expression) - c.Evaluate(bottom.Expression); } return CalculateIntegral(calc, distanceMethod, leftBound, rightBound); }
public static List<IntersectionInfo> CalculateIntersections(Calculator calc, IList<CalculatedLine> exprs) { // Find each possible pair of each line with no repeats // Asked how to do this in LINQ here: "http://stackoverflow.com/questions/3479980" var range = Enumerable.Range(0, exprs.Count).ToArray(); var pairs = from i in range from second in range.Skip(i + 1) select new { First = exprs[i], Second = exprs[second] }; // Calculate and refine each intersection in each pair var intrs = from pair in pairs.AsParallel().AsOrdered() let unrefined = FindRoughIntersections(pair.First, pair.Second) select from intersection in unrefined let refined = RefineIntersection(new Calculator(calc), (StandardExpression)pair.First.Expression, (StandardExpression)pair.Second.Expression, intersection) select new IntersectionInfo(pair.First.Expression.ToString(), pair.Second.Expression.ToString(), refined); return intrs.SelectMany(p => p).Distinct().ToList(); }
private void OnLoad(object sender, EventArgs e) { Application.Idle += OnApplicationIdle; GL.ClearColor(Color.CornflowerBlue); GL.Enable(EnableCap.DepthTest); SetupViewport(); Context.VSync = true; _window = new Window3D(-50, 50, -50, 50, -50, 50); Calculator calc = new Calculator(); CompiledExpression expr = ExpressionCompiler.CompileInfix("sin(x)+cos(y)", false); var mesh = LineCalculator3D.Calculate(calc, expr, _window, 0.5F); // Vertices GL.GenBuffers(1, out _bufferInfo.VertexId); GL.BindBuffer(BufferTarget.ArrayBuffer, _bufferInfo.VertexId); GL.BufferData(BufferTarget.ArrayBuffer, mesh.VertexArraySize, mesh.Vertices, BufferUsageHint.StaticDraw); // Indices GL.GenBuffers(1, out _bufferInfo.IndexId); GL.BindBuffer(BufferTarget.ElementArrayBuffer, _bufferInfo.IndexId); GL.BufferData(BufferTarget.ElementArrayBuffer, mesh.IndexArraySize, mesh.Indices, BufferUsageHint.StaticDraw); // Colors GL.GenBuffers(1, out _bufferInfo.ColorId); GL.BindBuffer(BufferTarget.ArrayBuffer, _bufferInfo.ColorId); GL.BufferData(BufferTarget.ArrayBuffer, mesh.ColorArraySize, mesh.Colors, BufferUsageHint.StaticDraw); _bufferInfo.IndexCount = mesh.Indices.Length; }
/// <summary> /// Initializes a new instance of the Calculator class using data from another <see cref="Calculator"/> /// instance. /// </summary> /// <param name="calcToCopy">The <see cref="Calculator"/> instance to copy data from into this new /// instance.</param> public Calculator(Calculator calcToCopy) { // Copy the dictionaries into a new instance _arguments = calcToCopy._arguments.ToDictionary(pair => pair.Key, pair => pair.Value); _funcs = calcToCopy._funcs.ToDictionary(pair => pair.Key, pair => pair.Value); }
public static double CalculateDerivative(Calculator calc, StandardExpression expr, double x, out double y) { calc.GraphingArgumentValue = x; double y1 = calc.Evaluate(expr.Expression); calc.GraphingArgumentValue = x + DeltaX; double y2 = calc.Evaluate(expr.Expression); y = y1; // lim h -> 0 (f(x + h) - f(x)) / h = slope of the derivative return (y2 - y1) / DeltaX; }
public static CompiledDomain CompileDomain(Calculator calc, IList<Constraint> domain) { // Signature: bool Domain(CompiledExpression[] leftRightExprs, Calculator calc) DynamicMethod method = GetDomainDynamicMethod(); ILGenerator il = method.GetILGenerator(); if (domain == null || domain.Count == 0) { return EmptyDomain; } List<CompiledExpression> expressions = new List<CompiledExpression>(); foreach (Constraint constraint in domain) { CompiledExpression left = ExpressionCompiler.CompileInfix(constraint.LeftValue); CompiledExpression right = ExpressionCompiler.CompileInfix(constraint.RightValue); Label success = il.DefineLabel(); PushExpressionValue(il, left, expressions, calc); PushExpressionValue(il, right, expressions, calc); // Check for the equality to be true OpCode op = GetOpCodeFromEquality(constraint.Equality); if (op == default(OpCode)) { // Not equal to il.Emit(OpCodes.Ceq); il.Emit(OpCodes.Brfalse, success); } else { il.Emit(op, success); } // Left side was not true, return false il.Emit(OpCodes.Ldc_I4_0); // Push 0 (represents false) il.Emit(OpCodes.Ret); // Return // Left side was true, test the right side now il.MarkLabel(success); } // Left and right (if there was a right side) were right, return true il.Emit(OpCodes.Ldc_I4_1); // Push 1 (represents true) il.Emit(OpCodes.Ret); // Return return new CompiledDomain(method, expressions.ToArray(), domain); }
private static PointD FindMostSignificant(Calculator calc, CalculatedLine line, double leftBound, double rightBound, Mode mode) { if (line.Expression.Type != ExpressionType.Linear) { throw new ArgumentException("The line must be a linear one."); } if (line.PointData.Count == 0) { throw new ArgumentException("The calculated line must contain at least one line segment."); } RoughSignificant roughMax = GetRoughSignificant(line, calc, leftBound, rightBound, mode); return RefineSignificant(calc, line, roughMax, mode); }
/// <summary> /// Initializes a new instance of the Graph class. /// </summary> public Graph() { _drawHelper = new GraphDrawingHelper(this); _integralDrawHelper = new IntegralDrawingHelper(this); _resolution = 1; _window = Window.Standard; Calculator = new Calculator(); _zoom = new ZoomMouseAdapter(this); _zoom.Zoom += delegate(object sender, GenericEventArgs<Window> e) { Window = e.Data; RestoreDefaultAdapter(); var handler = WindowChanged; if (handler != null) { handler(this, EventArgs.Empty); } }; _xRange = new XRangeMouseAdapter(this); _xRange.XRangeCreated += delegate(object sender, XRangeCreatedEventArgs e) { RestoreDefaultAdapter(); var handler = XRangeCreated; if (handler != null) { handler(this, e); } }; _dragging = new DraggingMouseAdapter(this); _current = _dragging; // Prevents flickering DoubleBuffered = true; ResizeRedraw = true; }
public static double CalculateDerivative(Calculator calc, ParametricExpression expr, double t, out PointD result) { calc.GraphingArgumentValue = t; double x1 = calc.Evaluate(expr.XExpression); double y1 = calc.Evaluate(expr.YExpression); calc.GraphingArgumentValue = t + DeltaX; double x2 = calc.Evaluate(expr.XExpression); double y2 = calc.Evaluate(expr.YExpression); double fPrime1 = (x2 - x1) / DeltaX; double fPrime2 = (y2 - y1) / DeltaX; result = new PointD(x1, y1); return fPrime2 / fPrime1; }
public SelectValue(Calculator calc, IEnumerable<Expression> criteria) { InitializeComponent(); _calc = calc; foreach (Expression expr in criteria) { listboxLines.Items.Add(expr); _exprs.Add(expr); } if (listboxLines.Items.Count == 0) { throw new Exception(); } listboxLines.SelectedIndex = 0; }
private static PointD[] CalculateLine(Calculator calc, CalculatedLine line, double end, ref double pos) { if (pos > end) { throw new ArgumentException("The current position is greater than the end."); } int i = 0; var filled = new PointD[(int)Math.Round(Math.Abs(end - pos) / line.Increment)]; while (end - pos > 1E-10 || pos < end) { calc.GraphingArgumentValue = pos; filled[i++] = new PointD(pos * line.XScale, calc.Evaluate(((StandardExpression)line.Expression).Expression) * line.YScale); pos += line.Increment; } return filled; }
public CalculateIntegral(Calculator calc, IEnumerable<StandardExpression> exprs) { InitializeComponent(); _calc = calc; _exprs = new List<StandardExpression>(); foreach (var expr in exprs) { listboxDefiniteIntegralList.Items.Add(expr); _exprs.Add(expr); } OnRadioButtonCheckedChanged(null, EventArgs.Empty); if (listboxDefiniteIntegralList.Items.Count > 0) { listboxDefiniteIntegralList.SelectedIndex = 0; textboxLeftBound.Select(); } }
/// <summary> /// Finds every point on the specified line where that line crosses the X axis. /// </summary> /// <param name="calc">The calculator used to evaluate the expression.</param> /// <param name="line">The line to test for zeros.</param> /// <returns> /// A list of points where the specified line crosses the X axis. If the line does not /// cross the X axis at any point, the list will be empty. /// </returns> /// <remarks><para> /// The algorithm used to find the zeros of a line is similar to the binary search /// algorithm. /// </para><para> /// First, each point on the line is iterated through. In each iteration, it checks if the /// sign of the Y values of the current point and the last point on the line are different. /// For example, if we are iterating through the line "5x + 5," once we iterate through the /// point (-1.5, -2.5) and (-0.5, 2.5), we find that the Y values have changed from negative /// to positive. Those two points are then stored for later use. We repeat this process until /// we reach the end of the line. /// </para><para> /// After we store each place where the Y values change signs, the binary search begins. /// In a single iteration, we first determine if the left point of the pair (the minimum) /// begins in the negative or positive side of the X axis. If it begins in the negative, it /// is said to be moving "upward." Otherwise, it is moving "downward." This is important /// because it determines what values are set to the high and low values for the binary /// search. One of two things then happen: /// </para><para> /// In the case of the line going upward, if the sign of the middle value is negative, then /// the low value becomes the middle value for the next iteration. Otherwise, the high value /// becomes the middle value. /// </para><para> /// In the case of the line going downward, if the sign of the middle value is positive, then /// the low value becomes the middle value for the next iteration. Otherwise, the high value /// becomes the middle value. /// </para><para> /// This continues until the middle values for two consecutive iterations are the same value. /// Once this happens, the limits of the precision and accuracy of the datatype we are using /// (64-bit floating point in this specific implementation's case) has been reached, and the /// search stops. The final middle value is the X value of the zero. /// </para></remarks> public static List<PointD> FindZeros(Calculator calc, CalculatedLine line) { if (line.Expression.Type != ExpressionType.Linear) { throw new ArgumentException("The expression type must be Linear."); } // Each PointF array represents the two points where the // sign changes from positive to negative or vice versa var signChanges = from roughZero in line.PointData.SelectMany(FindRoughZero) select new { LowerBound = roughZero[0].X / line.XScale, UpperBound = roughZero[1].X / line.XScale, InitialSign = roughZero[0].Y < 0 ? Sign.Negative : Sign.Positive }; // Find the corresponding zero for each sign change var zeros = from change in signChanges select RefineZero(calc, (StandardExpression)line.Expression, change.LowerBound, change.UpperBound, change.InitialSign); return zeros.ToList(); }
private static PointD RefineZero(Calculator calc, StandardExpression expr, double lowerBound, double upperBound, Sign initialSign) { double lo = lowerBound; double hi = upperBound; double mid = (lo + hi) / 2; double lastMid; double y; int i = 0; do { calc.GraphingArgumentValue = mid; y = calc.Evaluate(expr.Expression); Sign currentSign = y < 0 ? Sign.Negative : Sign.Positive; if (currentSign == initialSign) { lo = mid; } else { hi = mid; } lastMid = mid; mid = (lo + hi) / 2; // The "i < 100" below is only necessary when running in release mode, without a debugger attached. // Otherwise, it goes into an infinite loop, even though mid and lastMid actually do equal eachother. // I have no idea why that is (though its probably a race condition of some kind, despite the fact // that I'm not doing any multithreading anywhere around here). i++; } while (mid != lastMid && i < 100); return new PointD(mid, y); }
public static PointD FindMinimum(Calculator calc, CalculatedLine line, double leftBound, double rightBound) { return FindMostSignificant(calc, line, leftBound, rightBound, Mode.Minimum); }
private static PointD RefineSignificant(Calculator calc, CalculatedLine line, RoughSignificant roughSig, Mode mode) { double lo = roughSig.Left.X / line.XScale; double hi = roughSig.Right.X / line.XScale; double mid = (lo + hi) / 2; double lastMid = double.NaN; var stdExpr = (StandardExpression)line.Expression; Func<double, int> derivComparer = mode == Mode.Maximum ? _maxDerivComparer : _minDerivComparer; double y; while (lastMid != mid) { double deriv = DerivativeCalculator.CalculateDerivative(calc, stdExpr, mid, out y); int result = derivComparer(deriv); if (result == -1) { lo = mid; } else if (result == 1) { hi = mid; } else { // Unlikely, but possible break; } lastMid = mid; mid = (lo + hi) / 2; } mid = Math.Round(mid, DerivativeCalculator.DeltaXPrecision); calc.GraphingArgumentValue = mid; y = calc.Evaluate(stdExpr.Expression); return new PointD(mid, y); }
private static RoughSignificant GetRoughSignificant(CalculatedLine line, Calculator calc, double leftBound, double rightBound, Mode mode) { IList<IList<PointD>> segments = line.PointData.Count > 1 ? GetCompleteLine(calc, line) : line.PointData; double scaledLeftBound = leftBound * line.XScale; double scaledRightBound = rightBound * line.XScale; Func<double, double, bool> comparer = mode == Mode.Maximum ? _maxValComparer : _minValComparer; var significant = new PointD(0, mode == Mode.Maximum ? double.MinValue : double.MaxValue); int signifSegment = -1, signifPoint = -1; for (int i = 0; i < segments.Count; i++) { var segment = segments[i]; for (int j = 0; j < segment.Count; j++) { var point = segment[j]; if (comparer(significant.Y, point.Y) && point.X >= scaledLeftBound && point.X <= scaledRightBound) { significant = point; signifSegment = i; signifPoint = j; } } } if (signifSegment == -1 || signifPoint == -1) { throw new Exception("No significant value could be found."); } // Get the points to the left and right of the rough maximum PointD left; PointD right; GetSurroundingPoints(segments, signifSegment, signifPoint, out left, out right); return new RoughSignificant(left, right); }
/// <summary> /// Calculates the area between, in the case of this overload, a horizontal line and a curve. /// </summary> /// <param name="calc">The <see cref="Calculator"/> to calculate the Y-values with.</param> /// <param name="topHorizontalLine">The horizontal line that defines the top of the /// integral.</param> /// <param name="bottom">The curve that defines the bottom of the integral</param> /// <param name="leftBound">The left bound of the integral.</param> /// <param name="rightBound">The right bound of the integral.</param> /// <returns> /// The area between the horizontal line <paramref name="topHorizontalLine"/> and the curve /// <paramref name="bottom"/>. /// </returns> public static double CalculateIntegral(Calculator calc, double topHorizontalLine, StandardExpression bottom, double leftBound, double rightBound) { return CalculateIntegral(calc, c => topHorizontalLine - c.Evaluate(bottom.Expression), leftBound, rightBound); }
private static PointD RefineIntersection(Calculator calc, StandardExpression expr1, StandardExpression expr2, IList<PointD> intersectionData) { // Whether or not expr1 starts out above expr2 bool oneOnTop = intersectionData[0].Y > intersectionData[2].Y; double lo = intersectionData[0].X; double hi = intersectionData[1].X; double mid = (lo + hi) / 2; double lastMid; do { calc.GraphingArgumentValue = mid; double y1 = calc.Evaluate(expr1.Expression); double y2 = calc.Evaluate(expr2.Expression); // Unlikely, but possible if (y1 == y2) { break; } // Check to see if the current "on top" status (whether expr1 // is above expr2) is consistent with the initial "on top" status. if ((y1 > y2) == oneOnTop) { lo = mid; } else { hi = mid; } lastMid = mid; mid = (lo + hi) / 2; } while (mid != lastMid); // Recalculate the Y value against both expressions and return the average calc.GraphingArgumentValue = mid; return new PointD(mid, (calc.Evaluate(expr1.Expression) + calc.Evaluate(expr2.Expression)) / 2).CorrectForGdiLimit(); }
private static double CalculateAreaChunk(Calculator calc, Func<Calculator, double> distanceMethod, double start, double rightBound, int iterCount) { iterCount++; double totalArea = 0; double lastX = start; calc.GraphingArgumentValue = start; // The left side of the trapezoid double a = distanceMethod(calc); bool calcEnd = false; for (int i = 1; i < iterCount; i++) { double x = lastX + Increment; if (x > rightBound) { x = rightBound; calcEnd = true; } // Height of a sideways trapezoid calc.GraphingArgumentValue = x; double b = distanceMethod(calc); // Area of trapezoid = (a + b) / 2 * h // In this case, the trapezoid is sideways, so the height is the // increment, or the space between the previous and current iteration totalArea += (a + b) / 2 * (x - lastX); lastX = x; a = b; if (calcEnd) { break; } } return totalArea; }
private static double CalculateIntegral(Calculator calc, Func<Calculator, double> distanceMethod, double leftBound, double rightBound) { // Protip: the Wolfram Alpha way of verifying the results from this is: // "integral of (<<top>> - <<bottom>>)dx between <<start>> and <<stop>>" int numCores = Environment.ProcessorCount; int iterCount = (int)Math.Ceiling((rightBound - leftBound) / Increment); int itersPerCore = (int)Math.Floor((double)iterCount / numCores); // The number of iterations that the last thread should do (to accomodate for rounding errors) int lastCoreIters = itersPerCore + (iterCount % numCores); // Assemble the worker threads Task<double>[] tasks = new Task<double>[numCores]; int core = 0; bool lastTask = false; while (!lastTask) { lastTask = core + 1 >= numCores; double start = leftBound + ((core * itersPerCore) * Increment); int numIters = (lastTask ? lastCoreIters : itersPerCore); tasks[core++] = new Task<double>(() => CalculateAreaChunk(new Calculator(calc), distanceMethod, start, rightBound, numIters)); } foreach (Task<double> t in tasks) { t.Start(); } Task.WaitAll(tasks); double totalArea = tasks.Sum(t => t.Result); // Find the number of decimal places the increment goes to int decPlace = 0; double slicedIncrement = Increment; // Move the decimal point of the increment to the right on each iteration. We keep // moving the decimal over until the first significant digit of the increment is found while ((int)(slicedIncrement *= 10) == 0) { decPlace++; } // Assume the area is only accurate to the number of decimal places that the increment has return Math.Round(totalArea, decPlace + 1); }
public bool Evaluate(Calculator calc) { return _compiledDomain(calc); }
/// <summary> /// Evalutes the compiled expression with the specified arguments. /// </summary> /// <returns></returns> public double Evaluate(double[] arguments, Calculator calc) { return _compiled(arguments, calc); }
private static void PushExpressionValue(ILGenerator il, CompiledExpression left, List<CompiledExpression> expressions, Calculator calc) { if (left.Arguments.Count > 0 || left.GraphingArgumentPresent) { // Push the Calculator il.Emit(OpCodes.Ldarg_1); // Evaluate and push the left side of the equality il.Emit(OpCodes.Ldarg_0); // Push the expression array int index; if ((index = expressions.IndexOf(left)) != -1) { il.Emit(OpCodes.Ldelem, index); } else { il.Emit(OpCodes.Ldc_I4, expressions.Count); il.Emit(OpCodes.Ldelem, typeof(CompiledExpression)); // Push the left expression expressions.Add(left); } il.Emit(OpCodes.Call, _evaluateInfo); // Call Calculator.Evaluate(), pushing the result } else { // Push the static result of the left side's evaluation il.Emit(OpCodes.Ldc_R8, calc.Evaluate(left)); } }
/// <summary> /// Calculates the area between, in the case of this overload, a curve and a horizontal line. /// </summary> /// <param name="calc">The <see cref="Calculator"/> to calculate the Y-values with.</param> /// <param name="top">The curve that defines the top of the integral.</param> /// <param name="bottomHorizontalLine">The horizontal line that defines the bottom of the /// integral.</param> /// <param name="leftBound">The left bound of the integral.</param> /// <param name="rightBound">The right bound of the integral.</param> /// <returns> /// The area between the curve <paramref name="top"/> and the horizontal line /// <paramref name="bottomHorizontalLine"/>. /// </returns> public static double CalculateIntegral(Calculator calc, StandardExpression top, double bottomHorizontalLine, double leftBound, double rightBound) { return CalculateIntegral(calc, c => c.Evaluate(top.Expression) - bottomHorizontalLine, leftBound, rightBound); }
/// <summary> /// Evaluates the expression represented by this function using the specified arguments. /// </summary> /// <param name="localArgs">The arguments used to evaluate the expression. The order of the values in the /// array should be the same as those in <see cref="ExplicitArguments"/>.</param> /// <param name="calc">The calculator used to evaluate the function.</param> /// <returns>The result of the evaluation of the expression this function represents.</returns> /// <exception cref="ArgumentException">Thrown when the number of values in <paramref name="localArgs"/> /// does not match the number of values in <see cref="ExplicitArguments"/>.</exception> public double Evaluate(double[] localArgs, Calculator calc) { if (localArgs.Length != ExplicitArguments.Count) { throw new ArgumentException("The size of the arguments must match the size of ExplicitArguments."); } double[] args = new double[Expression.ArgumentCount]; // Set all the local arguments for (int i = 0; i < localArgs.Length; i++) { int argIndex = _explicitArgIndices[i]; if (argIndex == -1) { // The argument is not used in the expression continue; } args[argIndex] = localArgs[i]; } return Expression.Evaluate(args, calc); }
public static double CalculateDerivative(Calculator calc, StandardExpression expr, double x) { double y; return CalculateDerivative(calc, expr, x, out y); }
private static IList<IList<PointD>> GetCompleteLine(Calculator calc, CalculatedLine line) { var regions = new List<IList<PointD>>(); double xScale = line.XScale; double yScale = line.YScale; int segmentIndex = 1; int projSegments = line.PointData.Count - 1; double pos = line.Window.MinimumX; if (Math.Abs(line.PointData[0][0].X / xScale - pos) > 1E-10) { // There is a void region between the left side of the window and the first segment segmentIndex = 0; projSegments++; } for (int i = 0; i < projSegments; i++) { PointD[] filled = CalculateLine(calc, line, line.PointData[segmentIndex][0].X / xScale, ref pos); regions.Add(filled); regions.Add(line.PointData[segmentIndex]); pos = line.PointData[segmentIndex++].Last().X / xScale + line.Increment; } IList<PointD> lastSegment = line.PointData.Last(); if (Math.Abs(line.Window.MaximumX - lastSegment.Last().X / xScale) > 1E-10) { // There is a void region between the last segment and the right side of the window regions.Add(CalculateLine(calc, line, line.Window.MaximumX, ref pos)); } return regions; }
public static ConstructedMatrix Calculate(Calculator calc, CompiledExpression expr, Window3D window, float step) { float lowZ = float.MaxValue, highZ = float.MinValue; List<Vector3> results = new List<Vector3>(); List<int> startPoints = new List<int>(); int i = 0; for (float x = window.MinimumX; x < window.MaximumX; x += step) { startPoints.Add(i); calc.SetArgument("x", x); bool outsideGraph = true; Vector3? last = null; for (float y = window.MinimumY; y < window.MaximumY; y += step) { calc.SetArgument("y", y); float z = (float)calc.Evaluate(expr); Vector3 result = new Vector3(x, y, z); results.Add(result); bool includeResult = false, includeLast = false; if (z > window.MaximumZ || z < window.MinimumZ) { // Outside the window if (!outsideGraph) { // First point outside the window includeResult = true; outsideGraph = true; } } else { // Inside the window if (outsideGraph) { // First point inside the window, include the last vertex as well includeLast = true; } includeResult = true; outsideGraph = false; } if (includeResult) { if (z < lowZ) { lowZ = includeLast && last.HasValue ? Math.Min(z, last.Value.Z) : z; } if (z > highZ) { highZ = includeLast && last.HasValue ? Math.Max(z, last.Value.Z) : z; } } last = result; i++; } } List<int> indices = new List<int>(); for (int x = 0; x < startPoints.Count - 1; x++) { int currentX = startPoints[x]; int nextX = startPoints[x + 1]; for (int y = 0; y < startPoints[x + 1] - startPoints[x] - 1; y++) { indices.AddRange(new[] { currentX + y, nextX + y, nextX + y + 1, currentX + y, nextX + y + 1, currentX + y + 1 }); } } int[] colors = new int[results.Count]; float absLowZ = Math.Abs(lowZ); float mulFactor = 255F / (absLowZ + Math.Abs(highZ)); for (int j = 0; j < results.Count; j++) { float z = results[j].Z; int normalized = Math.Max(0, Math.Min(255, (int)Math.Round((z + absLowZ) * mulFactor))); colors[j] = Color.FromArgb(normalized, normalized, normalized).ToArgb(); } return new ConstructedMatrix(results.ToArray(), indices.ToArray(), colors.ToArray()); }