/// <summary> /// Solves a general equation \f$a_0 + a_1 x + a_2 x^2 = 0\f$, returning all real values of /// \f$x\f$ for which the equation is true using the 'abc-formula'. /// </summary> public static IEnumerable <double> Solve(double a0, double a1, double a2) { DebugUtil.AssertFinite(a0, nameof(a0)); DebugUtil.AssertFinite(a1, nameof(a1)); DebugUtil.AssertFinite(a2, nameof(a2)); if (Math.Abs(a2) <= 0.005) { if (Math.Abs(a1) <= 0.005) { // There are no roots found: yield break; } else { // There is a single root, found from solving the linear equation with a1=0: yield return(-a0 / a1); } yield break; } double x1 = 0.5 * (-a1 + Math.Sqrt(a1 * a1 - 4.0 * a0 * a2)) / a2; double x2 = 0.5 * (-a1 - Math.Sqrt(a1 * a1 - 4.0 * a0 * a2)) / a2; if (Double.IsNaN(x1) == false) { yield return(x1); } if (Double.IsNaN(x2) == false) { yield return(x2); } }
/// <summary> /// Construct a new <c>Cylinder</c> around a central curve, the <c>centerCurve</c>. /// The radius at each point on the central axis is defined by a one-dimensional function <c>radius</c>. /// The cylinder is cut in a pie-slice along the length of the shaft, defined by the angles /// <c>startAngle</c> and <c>endAngle</c>. /// /// The surface is parametrized with the coordinates \f$t \in [0, 1]\f$ (along the length of the shaft) and /// \f$\phi \in [0, 2 \pi]\f$ (along the radial coordinate). /// /// This gives a pie-sliced cylindrical surface that is defined only between the angles <c>startAngle</c> /// and <c>endAngle</c>. For example, if <c>startAngle = 0.0f</c> and <c>endAngle = Math.PI</c>, one would /// get a cylinder that is sliced in half. /// </summary> /// <param name="centerCurve"> /// <inheritdoc cref="Cylinder.CenterCurve"/> /// </param> /// <param name="radius"> /// <inheritdoc cref="Cylinder.Radius"/> /// </param> public Cylinder(Curve centerCurve, double radius) { DebugUtil.AssertFinite(radius, nameof(radius)); this.CenterCurve = centerCurve; this.Radius = new ConstantFunction <dvec2, double>(radius); this.StartAngle = 0.0; this.EndAngle = 2.0 * Math.PI; }
/// <summary> /// A quadratic function defined by \f$q(x) = a_0 + a_1 x + a_2 x^2\f$. /// </summary> public QuadraticFunction(double a0, double a1, double a2) { DebugUtil.AssertFinite(a0, nameof(a0)); DebugUtil.AssertFinite(a1, nameof(a1)); DebugUtil.AssertFinite(a2, nameof(a2)); _a0 = a0; _a1 = a1; _a2 = a2; }
/// <summary> /// Get the value of this function \f$q(x)\f$ at the given x-position, or the value of the /// <c>derivative</c>th derivative of this function. Mathematically, this gives \f$q^{(n)}(x)\f$, where /// \f$n\f$ is equal to the <c>derivative</c> parameter. /// </summary> /// <param name="x">The x-coordinate at which the function is sampled.</param> /// <param name="derivative"> /// The derivative level that must be taken of the function. If <c>derivative</c> is <c>0</c>, this means /// no derivative is taken. If it has a value of <c>1</c>, the first derivative is taken, with a value of /// <c>2</c> the second derivative is taken and so forth. This allows you to take any derivative level /// of the function. /// </param> /// <exception cref="ArgumentOutOfRangeException"> /// The value that is sampled must lie between the outermost points on which the spline is defined. If /// <c>x</c> is outside that domain, an <c>ArgumentOutOfRangeException</c> is thrown. /// </exception> public override double GetNthDerivativeAt(double x, uint derivative) { // The input parameter must lie between the outer points, and must not be NaN: if (!(x >= Points.Key[0] && x <= Points.Key[Points.Count - 1])) { throw new ArgumentOutOfRangeException(nameof(x), $"Cannot interpolate at {x}, which is outside the interval given by the spline points."); } // Find the index `i` of the closest point to the right of the input `x` parameter, which is the right point // used to interpolate between. Therefore, `i-1` indicates the left point of the interval. int i = Points.Key.BinarySearch(x); // BinarySearch returns a bitwise complement of the index if the point is not exactly in the list, such as // when interpolating. To turn it into a valid index, we take the bitwise complement again if it is negative: if (i < 0) { i = ~i; } // If the index is zero, we are exactly on the first point in the list. We increment by one to get the value // of the first spline segment, to avoid an IndexOutOfRangeException later on: if (i == 0) { i++; } double x1 = Points.Key[i - 1]; double x2 = Points.Key[i]; double y1 = Points.Value[i - 1]; double y2 = Points.Value[i]; // Calculate and return the interpolated value: double dx = x2 - x1; double dy = y2 - y1; double slope = dy / dx; double local_x = x - x1; double local_y = local_x * slope; double global_y = local_y + y1; // Return the result of evaluating a derivative function, depending on the derivative level: switch (derivative) { case 0: DebugUtil.AssertFinite(global_y, nameof(global_y)); return(global_y); case 1: DebugUtil.AssertFinite(slope, nameof(slope)); return(slope); default: return(0.0); } }
/// <summary> /// Solves the equation \f$a0 + a1 x + a2 x^2 + a3 x^3 + a4 x^4 = 0\f$, returning all real values of /// \f$x\f$ for which the equation is true. See https://en.wikipedia.org/wiki/Quartic_function for the /// algorithm used. /// </summary> public static IEnumerable <double> Solve(double a0, double a1, double a2, double a3, double a4) { DebugUtil.AssertAllFinite(new double[] { a0, a1, a2, a3, a4 }, "a"); if (Math.Abs(a4) <= 0.005) { foreach (double v in CubicFunction.Solve(a0, a1, a2, a3)) { QuarticFunction f = new QuarticFunction(a0, a1, a2, a3, a4); yield return(f.NewtonRaphson(v)); } yield break; } double ba = a3 / a4; double ca = a2 / a4; double da = a1 / a4; // double ea = a0/a4; double p1 = a2 * a2 * a2 - 4.5 * a3 * a2 * a1 + 13.5 * a4 * a1 * a1 + 13.5 * a3 * a3 * a0 - 36.0 * a4 * a2 * a0; double q = a2 * a2 - 3.0 * a3 * a1 + 12.0 * a4 * a0; Complex p2 = p1 + Complex.Sqrt(-q * q * q + p1 * p1); Complex pow = Complex.Pow(p2, (1.0 / 3.0)); Complex p3 = q / (3.0 * a4 * pow) + pow / (3.0 * a4); Complex p4 = Complex.Sqrt(ba * ba / 4.0 - 2.0 * ca / (3.0) + p3); Complex p5 = a3 * a3 / (2.0 * a4 * a4) - 4.0 * a2 / (3.0 * a4) - p3; Complex p6 = (-ba * ba * ba + 4.0 * ba * ca - 8.0 * da) / (4.0 * p4); List <Complex> roots = new List <Complex> { -ba / (4.0) - p4 / 2.0 - 0.5 * Complex.Sqrt(p5 - p6), -ba / (4.0) - p4 / 2.0 + 0.5 * Complex.Sqrt(p5 - p6), -ba / (4.0) + p4 / 2.0 - 0.5 * Complex.Sqrt(p5 + p6), -ba / (4.0) + p4 / 2.0 + 0.5 * Complex.Sqrt(p5 + p6), }; foreach (Complex root in roots) { if (Math.Abs(root.Imaginary) <= 0.005) { DebugUtil.AssertFinite(root.Real, nameof(root.Real)); yield return(root.Real); } } }
/// <inheritdoc /> public override double GetNthDerivativeAt(double x, uint derivative) { DebugUtil.AssertFinite(x, nameof(x)); // Return a different function depending on the derivative level: switch (derivative) { case 0: return(_a0 + _a1 * x + _a2 * x * x); case 1: return(_a1 + 2.0f * _a2 * x); case 2: return(2.0f * _a2); default: return(0.0f); } }
/// <summary> /// Solves the equation \f$a0 + a1 x + a2 x^2 + a3 x^3 = 0\f$, returning all real values of /// \f$x\f$ for which the equation is true. See https://en.wikipedia.org/wiki/Cubic_equation for the /// algorithm used. /// </summary> public static IEnumerable <double> Solve(double a0, double a1, double a2, double a3) { DebugUtil.AssertFinite(a3, nameof(a3)); DebugUtil.AssertFinite(a2, nameof(a2)); DebugUtil.AssertFinite(a1, nameof(a1)); DebugUtil.AssertFinite(a0, nameof(a0)); if (Math.Abs(a3) <= 0.005) { foreach (double v in QuadraticFunction.Solve(a0, a1, a2)) { CubicFunction f = new CubicFunction(a0, a1, a2, a3); yield return(f.NewtonRaphson(v)); } yield break; } double delta0 = a2 * a2 - 3.0 * a3 * a1; double delta1 = 2.0 * a2 * a2 * a2 - 9.0 * a3 * a2 * a1 + 27.0 * a3 * a3 * a0; Complex p1 = Complex.Sqrt(delta1 * delta1 - 4.0 * delta0 * delta0 * delta0); // The sign we choose in the next equation is arbitrary. To prevent a divide-by-zero down the line, if p2 is // zero, we must choose the opposite sign to make it nonzero: Complex p2 = delta1 + p1; Complex c = Complex.Pow(0.5 * p2, (1.0 / 3.0)); Complex xi = -0.5 + 0.5 * Complex.Sqrt(-3.0); List <Complex> roots = new List <Complex> { -1.0 / (3.0 * a3) * (a2 + c + delta0 / c), -1.0 / (3.0 * a3) * (a2 + xi * c + delta0 / (xi * c)), -1.0 / (3.0 * a3) * (a2 + xi * xi * c + delta0 / (xi * xi * c)), }; foreach (Complex root in roots) { if (Math.Abs(root.Imaginary) <= 0.05) { DebugUtil.AssertFinite(root.Real, nameof(root.Real)); yield return(root.Real); } } }
/// <summary> /// Solves the equation \f$d + c x + b x^2 + a x^3 = 0\f$, returning all real values of /// \f$x\f$ for which the equation is true. See https://en.wikipedia.org/wiki/Cubic_equation for the /// algorithm used. /// </summary> public static IEnumerable <double> Solve(double d, double c, double b, double a) { DebugUtil.AssertFinite(a, nameof(a)); DebugUtil.AssertFinite(b, nameof(b)); DebugUtil.AssertFinite(c, nameof(c)); DebugUtil.AssertFinite(d, nameof(d)); if (Math.Abs(a) <= 0.005f) { foreach (double v in QuadraticFunction.Solve(d, c, b)) { yield return(v); } yield break; } double delta0 = b * b - 3.0 * a * c; double delta1 = 2.0 * b * b * b - 9.0 * a * b * c + 27.0 * a * a * d; Complex p1 = Complex.Sqrt(delta1 * delta1 - 4.0 * delta0 * delta0 * delta0); // The sign we choose in the next equation is arbitrary. To prevent a divide-by-zero down the line, if p2 is // zero, we must choose the opposite sign to make it nonzero: Complex p2 = delta1 + p1; Complex C = Complex.Pow(0.5 * p2, (1.0 / 3.0)); Complex xi = -0.5 + 0.5 * Complex.Sqrt(-3.0); List <Complex> roots = new List <Complex> { -1.0 / (3.0 * a) * (b + C + delta0 / C), -1.0 / (3.0 * a) * (b + xi * C + delta0 / (xi * C)), -1.0 / (3.0 * a) * (b + xi * xi * C + delta0 / (xi * xi * C)), }; foreach (Complex root in roots) { if (Math.Abs(root.Imaginary) <= 0.05) { DebugUtil.AssertFinite(root.Real, nameof(root.Real)); yield return(root.Real); } } }
public override double GetNthDerivativeAt(double x, uint derivative) { DebugUtil.AssertFinite(x, nameof(x)); // Return a different function depending on the derivative level: switch (derivative) { case 0: return(A0 + A1 * x + A2 * x * x + A3 * x * x * x + A4 * x * x * x * x); case 1: return(A1 + 2.0 * A2 * x + 3.0 * A3 * x * x + 4.0 * A4 * x * x * x); case 2: return(2.0 * A2 + 6.0 * A3 * x + 12.0 * A4 * x * x); case 3: return(6.0 * A3 + 24.0 * A4 * x); case 4: return(24.0 * A4); default: return(0.0); } }
public double GetNthDerivativeAt(double x, uint derivative) { DebugUtil.AssertFinite(x, nameof(x)); // Return a different function depending on the derivative level: switch (derivative) { case 0: return(a0 + a1 * x + a2 * x * x + a3 * x * x * x + a4 * x * x * x * x); case 1: return(a1 + 2.0 * a2 * x + 3.0 * a3 * x * x + 4.0 * a4 * x * x * x); case 2: return(2.0 * a2 + 6.0 * a3 * x + 12.0 * a4 * x * x); case 3: return(6.0 * a3 + 24.0 * a4 * x); case 4: return(24.0 * a4); default: return(0.0); } }
/// <summary> /// Solves a general equation \f$a_0 + a_1 x + a_2 x^2 = 0\f$, returning all real values of /// \f$x\f$ for which the equation is true using the 'abc-formula'. /// </summary> public static IEnumerable <double> Solve(double a0, double a1, double a2) { DebugUtil.AssertFinite(a0, nameof(a0)); DebugUtil.AssertFinite(a1, nameof(a1)); DebugUtil.AssertFinite(a2, nameof(a2)); if (Math.Abs(a2) <= 0.005) { if (Math.Abs(a1) <= 0.005) { // There are no roots found: yield break; } else { // There is a single root, found from solving the linear equation with a1=0. We find a point close // to the root using the linear approximation, after which we approach the true solution using the // Newton-Raphson method: QuadraticFunction f = new QuadraticFunction(a0, a1, a2); yield return(f.NewtonRaphson(-a0 / a1)); } yield break; } double x1 = 0.5 * (-a1 + Math.Sqrt(a1 * a1 - 4.0 * a0 * a2)) / a2; double x2 = 0.5 * (-a1 - Math.Sqrt(a1 * a1 - 4.0 * a0 * a2)) / a2; if (Double.IsNaN(x1) == false) { yield return(x1); } if (Double.IsNaN(x2) == false) { yield return(x2); } }
/// <inheritdoc /> public double RayIntersect(Ray ray) { dvec3 rayStart = ray.StartPosition; dvec3 rayDirection = ray.Direction; DebugUtil.AssertAllFinite(rayStart, nameof(rayStart)); DebugUtil.AssertAllFinite(rayDirection, nameof(rayDirection)); // Since we raytrace only using a cylindrical surface that is horizontal and at the origin, we // first shift and rotate the ray such that we get the right orientation: dvec3 start = CenterCurve.GetStartPosition(); dvec3 end = CenterCurve.GetEndPosition(); DebugUtil.AssertAllFinite(start, nameof(start)); DebugUtil.AssertAllFinite(end, nameof(end)); dvec3 tangent = CenterCurve.GetTangentAt(0.0).Normalized; dvec3 normal = CenterCurve.GetNormalAt(0.0).Normalized; dvec3 binormal = CenterCurve.GetBinormalAt(0.0).Normalized; DebugUtil.AssertAllFinite(tangent, nameof(tangent)); DebugUtil.AssertAllFinite(normal, nameof(normal)); DebugUtil.AssertAllFinite(binormal, nameof(binormal)); double length = dvec3.Distance(start, end); DebugUtil.AssertFinite(length, nameof(length)); // CenterCurve is guaranteed to be a LineSegment, since the base property CenterCurve is masked by this // class' CenterCurve property that only accepts a LineSegment, and similarly this class' constructor only // accepts a LineSegment. The following mathematics, which assumes that the central axis is a line segment, // is therefore valid. dmat3 rotationMatrix = new dmat3(normal, binormal, tangent / length).Transposed; dvec3 rescaledRay = rotationMatrix * (rayStart - start); dvec3 newDirection = rotationMatrix * rayDirection.Normalized; double x0 = rescaledRay.x; double y0 = rescaledRay.y; double z0 = rescaledRay.z; double a = newDirection.x; double b = newDirection.y; double c = newDirection.z; // Raytrace using a cylindrical surface equation x^2 + y^2. The parameters in the following line // represent the coefficients of the expanded cylindrical surface equation, after the substitution // x = x_0 + a t and y = y_0 + b t: QuarticFunction surfaceFunction = new QuarticFunction(x0 * x0 + y0 * y0, 2.0 * (x0 * a + y0 * b), a * a + b * b, 0.0, 0.0); IEnumerable <double> intersections = Radius.SolveRaytrace(surfaceFunction, z0, c); // The previous function returns a list of intersection distances. The value closest to 0.0f represents the // closest intersection point. double minimum = Single.PositiveInfinity; foreach (double i in intersections) { // Calculate the 3d point at which the ray intersects the cylinder: dvec3 intersectionPoint = rayStart + i * rayDirection; // Find the closest point to the intersectionPoint on the centerLine. // Get the vector v from the start of the cylinder to the intersection point: dvec3 v = intersectionPoint - start; // ...And project this vector onto the center line: double t = -dvec3.Dot(intersectionPoint, tangent * length) / (length * length); // Now we have the parameter t on the surface of the SymmetricCylinder at which the ray intersects. // Find the angle to the normal of the centerLine, so that we can determine whether the // angle is within the bound of the pie-slice at position t: dvec3 centerLineNormal = CenterCurve.GetNormalAt(t); dvec3 centerLineBinormal = CenterCurve.GetBinormalAt(t); dvec3 d = intersectionPoint - CenterCurve.GetPositionAt(t); double correctionShift = Math.Sign(dvec3.Dot(d, centerLineBinormal)); double phi = (correctionShift * Math.Acos(dvec3.Dot(d, centerLineNormal))) % (2.0 * Math.PI); // Determine if the ray is inside the pie-slice of the cylinder that is being displayed, // otherwise discard: if (phi > StartAngle.GetValueAt(t) && phi < EndAngle.GetValueAt(t) && i >= 0.0) { minimum = Math.Sign(i) * Math.Min(Math.Abs(minimum), Math.Abs(i)); } } return(minimum); }