Exemplo n.º 1
0
		public static Vector2D GetPointOnCurve(CurveSegment segment, float delta) 
		{
			if(segment.CurveType == CurveSegmentType.QUADRATIC)
				return GetPointOnQuadraticCurve(segment.Start, segment.CPMid, segment.End, delta);

			if(segment.CurveType == CurveSegmentType.CUBIC)
				return GetPointOnCubicCurve(segment.Start, segment.End, segment.CPStart, segment.CPEnd, delta);

			if(segment.CurveType == CurveSegmentType.LINE)
				return GetPointOnLine(segment.Start, segment.End, delta);

			throw new Exception("GetPointOnCurve: got unknown curve type: " + segment.CurveType);
		}
Exemplo n.º 2
0
 public static void CreateCubicCurve(CurveSegment segment, int steps)
 {
     segment.CurveType = CurveSegmentType.CUBIC;
     segment.Points    = GetCubicCurve(segment.Start, segment.End, segment.CPStart, segment.CPEnd, steps);
 }
Exemplo n.º 3
0
 public static void CreateQuadraticCurve(CurveSegment segment, int steps)
 {
     segment.CurveType = CurveSegmentType.QUADRATIC;
     segment.Points    = GetQuadraticCurve(segment.Start, segment.CPMid, segment.End, steps);
 }
Exemplo n.º 4
0
        //mxd. Ported from Cubic Bezier curve tools by Andy Woodruff (http://cartogrammar.com/source/CubicBezier.as)
        //"default" values: z = 0.5, angleFactor = 0.75; if targetSegmentLength <= 0, will return lines
        public static Curve CurveThroughPoints(List <Vector2D> points, float z, float angleFactor, int targetSegmentLength)
        {
            Curve result = new Curve();

            // First calculate all the curve control points
            // None of this junk will do any good if there are only two points
            if (points.Count > 2 && targetSegmentLength > 0)
            {
                List <List <Vector2D> > controlPts = new List <List <Vector2D> >();             // An array to store the two control points (of a cubic Bézier curve) for each point

                // Make sure z is between 0 and 1 (too messy otherwise)
                if (z <= 0)
                {
                    z = 0.1f;
                }
                else if (z > 1)
                {
                    z = 1;
                }

                // Make sure angleFactor is between 0 and 1
                if (angleFactor < 0)
                {
                    angleFactor = 0;
                }
                else if (angleFactor > 1)
                {
                    angleFactor = 1;
                }

                // Ordinarily, curve calculations will start with the second point and go through the second-to-last point
                int firstPt = 1;
                int lastPt  = points.Count - 1;

                // Check if this is a closed line (the first and last points are the same)
                if (points[0].x == points[points.Count - 1].x && points[0].y == points[points.Count - 1].y)
                {
                    // Include first and last points in curve calculations
                    firstPt = 0;
                    lastPt  = points.Count;
                }
                else
                {
                    controlPts.Add(new List <Vector2D>());                    //add a dummy entry
                }

                // Loop through all the points (except the first and last if not a closed line) to get curve control points for each.
                for (int i = firstPt; i < lastPt; i++)
                {
                    // The previous, current, and next points
                    Vector2D p0 = (i - 1 < 0) ? points[points.Count - 2] : points[i - 1]; // If the first point (of a closed line), use the second-to-last point as the previous point
                    Vector2D p1 = points[i];
                    Vector2D p2 = (i + 1 == points.Count) ? points[1] : points[i + 1];    // If the last point (of a closed line), use the second point as the next point

                    float a = Vector2D.Distance(p0, p1);                                  // Distance from previous point to current point
                    if (a < 0.001)
                    {
                        a = 0.001f;                                             // Correct for near-zero distances, a cheap way to prevent division by zero
                    }
                    float b = Vector2D.Distance(p1, p2);                        // Distance from current point to next point
                    if (b < 0.001)
                    {
                        b = 0.001f;
                    }
                    float c = Vector2D.Distance(p0, p2);                        // Distance from previous point to next point
                    if (c < 0.001)
                    {
                        c = 0.001f;
                    }

                    float cos = (b * b + a * a - c * c) / (2 * b * a);
                    // Make sure above value is between -1 and 1 so that Math.acos will work
                    if (cos < -1)
                    {
                        cos = -1;
                    }
                    else if (cos > 1)
                    {
                        cos = 1;
                    }

                    float C = (float)Math.Acos(cos);                     // Angle formed by the two sides of the triangle (described by the three points above) adjacent to the current point

                    // Duplicate set of points. Start by giving previous and next points values RELATIVE to the current point.
                    Vector2D aPt = new Vector2D(p0.x - p1.x, p0.y - p1.y);
                    Vector2D bPt = new Vector2D(p1.x, p1.y);
                    Vector2D cPt = new Vector2D(p2.x - p1.x, p2.y - p1.y);

                    /*
                     * We'll be adding adding the vectors from the previous and next points to the current point,
                     * but we don't want differing magnitudes (i.e. line segment lengths) to affect the direction
                     * of the new vector. Therefore we make sure the segments we use, based on the duplicate points
                     * created above, are of equal length. The angle of the new vector will thus bisect angle C
                     * (defined above) and the perpendicular to this is nice for the line tangent to the curve.
                     * The curve control points will be along that tangent line.
                     */
                    if (a > b)
                    {
                        aPt = aPt.GetNormal() * b;                                  // Scale the segment to aPt (bPt to aPt) to the size of b (bPt to cPt) if b is shorter.
                    }
                    else if (b > a)
                    {
                        cPt = cPt.GetNormal() * a;                                      // Scale the segment to cPt (bPt to cPt) to the size of a (aPt to bPt) if a is shorter.
                    }
                    // Offset aPt and cPt by the current point to get them back to their absolute position.
                    aPt += p1;
                    cPt += p1;

                    // Get the sum of the two vectors, which is perpendicular to the line along which our curve control points will lie.
                    float ax = bPt.x - aPt.x;                   // x component of the segment from previous to current point
                    float ay = bPt.y - aPt.y;
                    float bx = bPt.x - cPt.x;                   // x component of the segment from next to current point
                    float by = bPt.y - cPt.y;
                    float rx = ax + bx;                         // sum of x components
                    float ry = ay + by;

                    // Correct for three points in a line by finding the angle between just two of them
                    if (rx == 0 && ry == 0)
                    {
                        rx = -bx;                               // Really not sure why this seems to have to be negative
                        ry = by;
                    }

                    // Switch rx and ry when y or x difference is 0. This seems to prevent the angle from being perpendicular to what it should be.
                    if (ay == 0 && by == 0)
                    {
                        rx = 0;
                        ry = 1;
                    }
                    else if (ax == 0 && bx == 0)
                    {
                        rx = 1;
                        ry = 0;
                    }

                    //float r = (float)Math.Sqrt(rx * rx + ry * ry);	// length of the summed vector - not being used, but there it is anyway
                    float theta = (float)Math.Atan2(ry, rx);                               // angle of the new vector

                    float controlDist        = Math.Min(a, b) * z;                         // Distance of curve control points from current point: a fraction the length of the shorter adjacent triangle side
                    float controlScaleFactor = C / Angle2D.PI;                             // Scale the distance based on the acuteness of the angle. Prevents big loops around long, sharp-angled triangles.
                    controlDist *= ((1 - angleFactor) + angleFactor * controlScaleFactor); // Mess with this for some fine-tuning
                    float controlAngle = theta + Angle2D.PIHALF;                           // The angle from the current point to control points: the new vector angle plus 90 degrees (tangent to the curve).

                    Vector2D controlPoint2 = new Vector2D(controlDist, 0);
                    Vector2D controlPoint1 = new Vector2D(controlDist, 0);
                    controlPoint2 = controlPoint2.GetRotated(controlAngle);
                    controlPoint1 = controlPoint1.GetRotated(controlAngle + Angle2D.PI);

                    // Offset control points to put them in the correct absolute position
                    controlPoint1 += p1;
                    controlPoint2 += p1;

                    /*
                     * Haven't quite worked out how this happens, but some control points will be reversed.
                     * In this case controlPoint2 will be farther from the next point than controlPoint1 is.
                     * Check for that and switch them if it's true.
                     */
                    if (Vector2D.Distance(controlPoint2, p2) > Vector2D.Distance(controlPoint1, p2))
                    {
                        controlPts.Add(new List <Vector2D> {
                            controlPoint2, controlPoint1
                        });
                    }
                    else
                    {
                        controlPts.Add(new List <Vector2D> {
                            controlPoint1, controlPoint2
                        });
                    }
                }

                // If this isn't a closed line, draw a regular quadratic Bézier curve from the first to second points, using the first control point of the second point
                if (firstPt == 1)
                {
                    float        length   = (points[1] - points[0]).GetLength();
                    int          numSteps = Math.Max(1, (int)Math.Round(length / targetSegmentLength));
                    CurveSegment segment  = new CurveSegment();
                    segment.Start = points[0];
                    segment.CPMid = controlPts[1][0];
                    segment.End   = points[1];
                    CreateQuadraticCurve(segment, numSteps);

                    result.Segments.Add(segment);
                }

                // Loop through points to draw cubic Bézier curves through the penultimate point, or through the last point if the line is closed.
                for (int i = firstPt; i < lastPt - 1; i++)
                {
                    float length   = (points[i + 1] - points[i]).GetLength();
                    int   numSteps = Math.Max(1, (int)Math.Round(length / targetSegmentLength));

                    CurveSegment segment = new CurveSegment();
                    segment.CPStart = controlPts[i][1];
                    segment.CPEnd   = controlPts[i + 1][0];
                    segment.Start   = points[i];
                    segment.End     = points[i + 1];
                    CreateCubicCurve(segment, numSteps);

                    result.Segments.Add(segment);
                }

                // If this isn't a closed line, curve to the last point using the second control point of the penultimate point.
                if (lastPt == points.Count - 1)
                {
                    float length   = (points[lastPt] - points[lastPt - 1]).GetLength();
                    int   numSteps = Math.Max(1, (int)Math.Round(length / targetSegmentLength));

                    CurveSegment segment = new CurveSegment();
                    segment.Start = points[lastPt - 1];
                    segment.CPMid = controlPts[lastPt - 1][1];
                    segment.End   = points[lastPt];
                    CreateQuadraticCurve(segment, numSteps);

                    result.Segments.Add(segment);
                }

                // create lines
            }
            else if (points.Count >= 2)
            {
                for (int i = 0; i < points.Count - 1; i++)
                {
                    CurveSegment segment = new CurveSegment();
                    segment.Start  = points[i];
                    segment.End    = points[i + 1];
                    segment.Points = new[] { segment.Start, segment.End };
                    result.Segments.Add(segment);
                }
            }

            result.UpdateShape();
            return(result);
        }