/// <summary> /// Construct a linear spline using a set of input points. /// <example>For example: /// <code> /// SortedList<double, double> splinePoints = new SortedList<double, double>(); /// splinePoints.Add(0.0f, 1.1f); /// splinePoints.Add(0.3f, 0.4f); /// splinePoints.Add(1.0f, 2.0f); /// LinearSpline1D spline = new LinearSpline1D(splinePoints); /// </code> /// creates a linear spline that passes through three points: (0.0, 1.1), (0.3, 0.4) and (1.0, 2.0). /// </example> /// </summary> /// <param name="points">A list of points that is sorted by the x-coordinate. This collection is copied.</param> /// <exception cref="ArgumentException"> /// A linear spline must have at least two points to be properly defined. If <c>points</c> contains less than /// two points, the spline is undefined, so an <c>ArgumentException</c> is thrown. /// </exception> public LinearSpline1D(SortedList <double, double> points) { if (points.Count < 2) { if (points.Count == 1) { throw new ArgumentException("List contains only a single point. A spline must have at least two points.", nameof(points)); } else { throw new ArgumentException("List is empty. A spline must have at least two points.", nameof(points)); } } Points = new SortedPointsList <double>(points); DebugUtil.AssertAllFinite(Points, nameof(Points)); // Calculate the coefficients for each segment of the spline: _parameters = new double[points.Count]; // Recursively find the _parameters: for (int i = 1; i < points.Count; i++) { double dx = Points.Key[i] - Points.Key[i - 1]; double dy = Points.Value[i] - Points.Value[i - 1]; _parameters[i] = dy / dx; } DebugUtil.AssertAllFinite(_parameters, nameof(_parameters)); }
/// <summary> /// Find the solution to a tridiagonal matrix linear system Ax = d using the Thomas algorithm. /// </summary> private static double[] ThomasAlgorithm(double[] a, double[] b, double[] c, double[] d) { DebugUtil.AssertAllFinite(a, nameof(a)); DebugUtil.AssertAllFinite(b, nameof(b)); DebugUtil.AssertAllFinite(c, nameof(c)); DebugUtil.AssertAllFinite(d, nameof(d)); int size = d.Count(); // Perform forward sweep: double[] newC = new double[size]; double[] newD = new double[size]; newC[0] = c[0] / b[0]; newD[0] = d[0] / b[0]; for (int i = 1; i < size; i++) { newC[i] = c[i] / (b[i] - a[i] * newC[i - 1]); newD[i] = (d[i] - a[i] * newD[i - 1]) / (b[i] - a[i] * newC[i - 1]); } DebugUtil.AssertAllFinite(newC, nameof(newC)); DebugUtil.AssertAllFinite(newD, nameof(newD)); // Perform back substitution: double[] x = new double[size]; x[size - 1] = newD[size - 1]; for (int i = (size - 2); i >= 0; i--) { x[i] = newD[i] - newC[i] * x[i + 1]; } DebugUtil.AssertAllFinite(x, nameof(x)); return(x); }
/// <summary> /// Constructs a ShiftedMap2D whose input is shifted by a dvec2 and stretched by another dvec2. /// </summary> /// <param name="shift"> /// The vector by which the 2D map is shifted. /// </param> /// <param name="stretch"> /// The vector by which the 2D map's coordinates are multiplied. /// </param> /// <param name="function"> /// The ContinuousMap that is shifted and stretched. /// </param> public ShiftedMap2D(dvec2 shift, dvec2 stretch, ContinuousMap <dvec2, TOut> function) { DebugUtil.AssertAllFinite(shift, nameof(shift)); DebugUtil.AssertAllFinite(stretch, nameof(stretch)); this.Shift = shift; this.Stretch = stretch; this.Function = function; }
/// <summary> /// A quartic function defined by \f$q(x) = a_0 + a_1 x + a_2 x^2 + a_3 x^3 + a_4 x^4\f$. /// See https://en.wikipedia.org/wiki/Quartic_function for more information. /// </summary> public QuarticFunction(double a0, double a1, double a2, double a3, double a4) { DebugUtil.AssertAllFinite(new double[] { a0, a1, a2, a3, a4 }, "a"); this.A0 = a0; this.A1 = a1; this.A2 = a2; this.A3 = a3; this.A4 = a4; }
/// <inheritdoc /> public override dvec3 GetPositionAt(dvec2 uv) { DebugUtil.AssertAllFinite(uv, nameof(uv)); double v = uv.y; dvec3 translation = CenterCurve.GetPositionAt(v); double radius = Radius.GetValueAt(uv); return(translation + radius * GetNormalAt(uv)); }
/// <summary> /// Construct a new <c>Hemisphere</c> at the point <c>center</c>, pointing in the direction of /// <c>direction</c>. The radius is defined by a two-dimensional function <c>radius</c>. /// /// The surface is parametrized with the coordinates \f$u \in [0, 2\pi]\f$ (the azimuthal angle) and /// \f$v \in [0, \frac{1}{2} \pi]\f$ (the inclination angle). /// </summary> /// <param name="center"> /// The position of the hemisphere. /// </param> /// <param name="direction"> /// The vector that defines which way is 'forward' for the object. Will be normalized on initialization. /// </param> /// <param name="normal"> /// The vector that defines which way is 'up' for the object. Should be perpendicular to /// <c>direction</c> and <c>binormal</c>. Will be normalized on initialization. /// </param> /// <param name="binormal"> /// The vector that defines which way is 'left' for the object. Should be perpendicular to /// <c>direction</c> and <c>normal</c>. Will be normalized on initialization. /// </param> public Hemisphere(ContinuousMap <dvec2, double> radius, dvec3 center, dvec3 direction, dvec3 normal, dvec3 binormal) { DebugUtil.AssertAllFinite(center, nameof(center)); DebugUtil.AssertAllFinite(direction, nameof(direction)); DebugUtil.AssertAllFinite(normal, nameof(normal)); DebugUtil.AssertAllFinite(binormal, nameof(binormal)); this.Radius = radius; this.Center = center; this.Direction = direction; this.Normal = normal; this.Binormal = binormal; }
/// <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 dvec3 GetNormalAt(dvec2 uv) { DebugUtil.AssertAllFinite(uv, nameof(uv)); double u = uv.x; double v = uv.y; dvec3 curveTangent = CenterCurve.GetTangentAt(v).Normalized; dvec3 curveNormal = CenterCurve.GetNormalAt(v).Normalized; dvec3 curveBinormal = dvec3.Cross(curveTangent, curveNormal); double startAngle = StartAngle.GetValueAt(v); double endAngle = EndAngle.GetValueAt(v); return(Math.Cos(u) * curveNormal + Math.Sin(u) * curveBinormal); }
/// <inheritdoc /> public override double GetValueAt(dvec2 uv) { DebugUtil.AssertAllFinite(uv, nameof(uv)); double directionSign = (direction == RayCastDirection.Outwards) ? 1.0 : -1.0; Ray ray = new Ray(raycastSurface.GetPositionAt(uv), directionSign * raycastSurface.GetNormalAt(uv).Normalized); double intersectionRadius = moldSurface.RayIntersect(ray); if (Math.Abs(intersectionRadius) <= maxDistance) { return(intersectionRadius); } else { return(defaultRadius.GetValueAt(uv)); } }
/// <summary> /// Construct a linear spline using a set of input points. /// <example>For example: /// <code> /// SortedList{double, double} splinePoints = new SortedList{double, double}(); /// splinePoints.Add(0.0f, 1.1f); /// splinePoints.Add(0.3f, 0.4f); /// splinePoints.Add(1.0f, 2.0f); /// LinearSpline1D spline = new LinearSpline1D(splinePoints); /// </code> /// creates a linear spline that passes through three points: (0.0, 1.1), (0.3, 0.4) and (1.0, 2.0). /// </example> /// </summary> /// <param name="points">A list of points that is sorted by the x-coordinate. This collection is copied.</param> /// <exception cref="ArgumentException"> /// A linear spline must have at least two points to be properly defined. If <c>points</c> contains less than /// two points, the spline is undefined, so an <c>ArgumentException</c> is thrown. /// </exception> public LinearSpline1D(SortedList <double, double> points) { if (points.Count < 2) { if (points.Count == 1) { throw new ArgumentException("List contains only a single point. A spline must have at least two points.", nameof(points)); } else { throw new ArgumentException("List is empty. A spline must have at least two points.", nameof(points)); } } Points = new SortedPointsList <double>(points); DebugUtil.AssertAllFinite(Points, nameof(Points)); }
/// <inheritdoc /> public override dvec3 GetNormalAt(dvec2 uv) { DebugUtil.AssertAllFinite(uv, nameof(uv)); double u = uv.x; double v = uv.y; if ((v >= -0.5 * Math.PI) && (v < 0.0)) { return(startCap.GetNormalAt(new dvec2(u, v + 0.5 * Math.PI))); } else if ((v >= 0.0) && (v < 1.0)) { return(shaft.GetNormalAt(new dvec2(u, v))); } else if ((v >= 1.0) && (v <= 1.0 + 0.5 * Math.PI)) { return(endCap.GetNormalAt(new dvec2(u, 1.0 + 0.5 * Math.PI - v))); } else { throw new ArgumentOutOfRangeException("v", "'v' must be between [-0.5 pi] and [1.0 + 0.5 pi]."); } }
/// <inheritdoc /> public override TOut GetValueAt(dvec2 uv) { DebugUtil.AssertAllFinite(uv, nameof(uv)); return(Function.GetValueAt(Shift + Stretch * uv)); }
/// <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); }
/// <summary> /// Construct a cubic spline using a set of input points. /// <example>For example: /// <code> /// SortedList{double, double} splinePoints = new SortedList{double, double}(); /// splinePoints.Add(0.0f, 1.1f); /// splinePoints.Add(0.3f, 0.4f); /// splinePoints.Add(1.0f, 2.0f); /// CubicSpline1D spline = new CubicSpline1D(splinePoints); /// </code> /// creates a cubic spline that passes through three points: (0.0, 1.1), (0.3, 0.4) and (1.0, 2.0). /// </example> /// </summary> /// <param name="points">A list of points that is sorted by the x-coordinate. This collection is copied.</param> /// <exception cref="ArgumentException"> /// A cubic spline must have at least two points to be properly defined. If <c>points</c> contains less than /// two points, the spline is undefined, so an <c>ArgumentException</c> is thrown. /// </exception> public CubicSpline1D(SortedList <double, double> points) { if (points.Count < 2) { if (points.Count == 1) { throw new ArgumentException("List contains only a single point. A spline must have at least two points.", "points"); } else { throw new ArgumentException("List is empty. A spline must have at least two points.", "points"); } } Points = new SortedPointsList <double>(points); DebugUtil.AssertAllFinite(Points, nameof(Points)); // Calculate the coefficients of the spline: double[] a = new double[points.Count]; double[] b = new double[points.Count]; double[] c = new double[points.Count]; double[] d = new double[points.Count]; // Set up the boundary condition for a natural spline: { double x2 = 1.0 / (Points.Key[1] - Points.Key[0]); double y2 = Points.Value[1] - Points.Value[0]; a[0] = 0.0; b[0] = 2.0 * x2; c[0] = x2; d[0] = 3.0 * (y2 * x2 * x2); } // Set up the tridiagonal matrix linear system: for (int i = 1; i < points.Count - 1; i++) { double x1 = 1.0 / (Points.Key[i] - Points.Key[i - 1]); double x2 = 1.0 / (Points.Key[i + 1] - Points.Key[i]); double y1 = Points.Value[i] - Points.Value[i - 1]; double y2 = Points.Value[i + 1] - Points.Value[i]; a[i] = x1; b[i] = 2.0 * (x1 + x2); c[i] = x2; d[i] = 3.0 * (y1 * x1 * x1 + y2 * x2 * x2); } // Set up the boundary condition for a natural spline: { double x1 = 1.0 / (Points.Key[points.Count - 1] - Points.Key[points.Count - 2]); double y1 = (Points.Value[points.Count - 1] - Points.Value[points.Count - 2]); a[points.Count - 1] = x1; b[points.Count - 1] = 2.0 * x1; c[points.Count - 1] = 0.0; d[points.Count - 1] = 3.0 * (y1 * x1 * x1); } // Solve the linear system using the Thomas algorithm: this._parameters = ThomasAlgorithm(a, b, c, d); }