|
1 | 1 | /* |
2 | 2 | * COPYRIGHT: See COPYING in the top level directory |
3 | 3 | * PROJECT: LightVector |
4 | | - * FILE: LightVector/BezierCurveFactory.cs |
| 4 | + * FILE: BezierCurveFactory.cs |
5 | 5 | * PURPOSE: Generate specific Graphic Output Information |
6 | 6 | * PROGRAMER: Peter Geinitz (Wayfarer) |
7 | 7 | */ |
8 | 8 |
|
9 | 9 | using System; |
10 | 10 | using System.Collections.Generic; |
11 | | -using System.Linq; |
12 | 11 | using System.Numerics; |
13 | 12 | using System.Windows; |
14 | 13 | using System.Windows.Media; |
15 | | -using System.Windows.Shapes; |
16 | 14 |
|
17 | 15 | namespace LightVector |
18 | 16 | { |
19 | 17 | /// <summary> |
20 | | - /// Factory for generating Bezier curves from vector-based points. |
| 18 | + /// Factory for generating Bezier geometries from vector-based points. |
| 19 | + /// Converts Cardinal Splines (Tension-based) into Cubic Beziers (Control-point based). |
21 | 20 | /// </summary> |
22 | 21 | internal static class BezierCurveFactory |
23 | 22 | { |
24 | 23 | /// <summary> |
25 | | - /// Generates a Bezier curve from a list of points with a specified tension. |
| 24 | + /// Generates a Bezier geometry from a list of points with a specified tension. |
26 | 25 | /// </summary> |
27 | 26 | /// <param name="points">The vector points to define the curve.</param> |
28 | | - /// <param name="tension">The tension factor to adjust the curve's shape.</param> |
29 | | - /// <returns>The generated <see cref="Path" /> representing the Bezier curve.</returns> |
30 | | - internal static Path CreateBezierCurve(List<Vector2> points, double tension) |
| 27 | + /// <param name="tension">The tension factor (0.0 to 1.0 usually).</param> |
| 28 | + /// <returns>The generated <see cref="Geometry" />.</returns> |
| 29 | + internal static Geometry CreateBezierGeometry(List<Vector2> points, double tension) |
31 | 30 | { |
32 | 31 | if (points == null || points.Count < 2) |
33 | 32 | { |
34 | | - throw new ArgumentException("At least two points are required to create a Bezier curve."); |
| 33 | + return Geometry.Empty; |
35 | 34 | } |
36 | 35 |
|
37 | | - var resultPoints = GenerateCurvePoints(points, tension); |
38 | | - return BuildBezierPath(resultPoints.ToArray()); |
39 | | - } |
| 36 | + // 1. Calculate the Cubic Bezier points (Control1, Control2, Endpoint, ...) |
| 37 | + var segmentPoints = GenerateBezierPoints(points, tension); |
40 | 38 |
|
41 | | - /// <summary> |
42 | | - /// Builds a Bezier path using the calculated points. |
43 | | - /// </summary> |
44 | | - /// <param name="points">The calculated points defining the curve.</param> |
45 | | - /// <returns>The generated <see cref="Path" /> for the Bezier curve.</returns> |
46 | | - private static Path BuildBezierPath(IReadOnlyList<Point> points) |
47 | | - { |
48 | | - var path = new Path(); |
| 39 | + // 2. Build the Geometry |
49 | 40 | var pathGeometry = new PathGeometry(); |
50 | | - path.Data = pathGeometry; |
51 | 41 |
|
52 | | - var pathFigure = new PathFigure { StartPoint = points[0] }; |
53 | | - pathGeometry.Figures.Add(pathFigure); |
| 42 | + // The first point is the StartPoint of the figure |
| 43 | + var startPoint = new Point(points[0].X, points[0].Y); |
| 44 | + var pathFigure = new PathFigure { StartPoint = startPoint, IsClosed = false }; |
54 | 45 |
|
55 | | - var pathSegmentCollection = new PathSegmentCollection(); |
56 | | - pathFigure.Segments = pathSegmentCollection; |
57 | | - |
58 | | - var pointCollection = new PointCollection(); |
59 | | - for (var i = 1; i < points.Count; i++) |
| 46 | + // Add the segments (groups of 3 points) |
| 47 | + var polyBezierSegment = new PolyBezierSegment |
60 | 48 | { |
61 | | - pointCollection.Add(points[i]); |
62 | | - } |
| 49 | + Points = new PointCollection(segmentPoints) |
| 50 | + }; |
| 51 | + |
| 52 | + pathFigure.Segments.Add(polyBezierSegment); |
| 53 | + pathGeometry.Figures.Add(pathFigure); |
63 | 54 |
|
64 | | - var bezierSegment = new PolyBezierSegment { Points = pointCollection }; |
65 | | - pathSegmentCollection.Add(bezierSegment); |
| 55 | + // Freeze for performance since this geometry won't change after creation |
| 56 | + if (pathGeometry.CanFreeze) pathGeometry.Freeze(); |
66 | 57 |
|
67 | | - return path; |
| 58 | + return pathGeometry; |
68 | 59 | } |
69 | 60 |
|
70 | 61 | /// <summary> |
71 | | - /// Generates the points for the Bezier curve based on the input points and tension. |
| 62 | + /// Generates the flattened list of points [C1, C2, End, C1, C2, End...] for WPF. |
72 | 63 | /// </summary> |
73 | | - /// <param name="points">The input vector points.</param> |
74 | | - /// <param name="tension">The tension factor to modify the curve.</param> |
75 | | - /// <returns>The calculated control points for the curve.</returns> |
76 | | - private static IEnumerable<Point> GenerateCurvePoints(IReadOnlyList<Vector2> points, double tension) |
| 64 | + private static IEnumerable<Point> GenerateBezierPoints(IReadOnlyList<Vector2> points, double tension) |
77 | 65 | { |
78 | | - if (points.Count < 2) |
79 | | - { |
80 | | - return Enumerable.Empty<Point>(); |
81 | | - } |
82 | | - |
| 66 | + // Mathematical constant for smoothing Cardinal Splines |
83 | 67 | var controlScale = tension / 0.5 * 0.175; |
84 | | - var resultPoints = new List<Point> { new(points[0].X, points[0].Y) }; |
| 68 | + |
| 69 | + var resultPoints = new List<Point>(); |
85 | 70 |
|
86 | 71 | for (var i = 0; i < points.Count - 1; i++) |
87 | 72 | { |
88 | 73 | var ptBefore = points[Math.Max(i - 1, 0)]; |
89 | | - var pt = points[i]; |
| 74 | + var ptCurrent = points[i]; |
90 | 75 | var ptAfter = points[i + 1]; |
91 | 76 | var ptAfter2 = points[Math.Min(i + 2, points.Count - 1)]; |
92 | 77 |
|
93 | | - var dx = ptAfter.X - ptBefore.X; |
94 | | - var dy = ptAfter.Y - ptBefore.Y; |
95 | | - var p2 = new Point(pt.X + (controlScale * dx), pt.Y + (controlScale * dy)); |
96 | | - |
97 | | - dx = ptAfter2.X - pt.X; |
98 | | - dy = ptAfter2.Y - pt.Y; |
99 | | - |
100 | | - var p3 = new Point(ptAfter.X - (controlScale * dx), ptAfter.Y - (controlScale * dy)); |
101 | | - |
| 78 | + // Calculate Control Point 1 (p2) |
| 79 | + // Tangent based on previous and next point |
| 80 | + var dx1 = ptAfter.X - ptBefore.X; |
| 81 | + var dy1 = ptAfter.Y - ptBefore.Y; |
| 82 | + var p2 = new Point( |
| 83 | + ptCurrent.X + (controlScale * dx1), |
| 84 | + ptCurrent.Y + (controlScale * dy1)); |
| 85 | + |
| 86 | + // Calculate Control Point 2 (p3) |
| 87 | + // Tangent based on current and next-next point |
| 88 | + var dx2 = ptAfter2.X - ptCurrent.X; |
| 89 | + var dy2 = ptAfter2.Y - ptCurrent.Y; |
| 90 | + var p3 = new Point( |
| 91 | + ptAfter.X - (controlScale * dx2), |
| 92 | + ptAfter.Y - (controlScale * dy2)); |
| 93 | + |
| 94 | + // WPF PolyBezierSegment requires exactly 3 points per segment: |
| 95 | + // 1. Control Point 1 |
| 96 | + // 2. Control Point 2 |
| 97 | + // 3. End Point |
102 | 98 | resultPoints.Add(p2); |
103 | 99 | resultPoints.Add(p3); |
| 100 | + resultPoints.Add(new Point(ptAfter.X, ptAfter.Y)); // <-- THIS WAS MISSING |
104 | 101 | } |
105 | 102 |
|
106 | | - // No need to add the last point (p4) since it's already included in the original list of points |
107 | | - resultPoints.Add(new Point(points.Last().X, points.Last().Y)); |
108 | | - |
109 | 103 | return resultPoints; |
110 | 104 | } |
111 | 105 | } |
|
0 commit comments