/// <summary>Slices off anything below the given line.</summary> public override void SliceBottom(float sliceLine, VectorPath path) { float previous = Previous.Y; float current = Y; // Both below? if (previous < sliceLine && current < sliceLine) { // Both below. path.RemoveVisually(this); return; } else if (previous >= sliceLine && current >= sliceLine) { // Do nothing. return; } // Split where the slice line is. VectorPoint newPoint = Split((sliceLine - previous) / (current - previous), path); // Delete the segment to the left/right. if (current < sliceLine) { // Delete this -> newPoint path.RemoveVisually(newPoint); } else { // Delete previous -> this path.RemoveVisually(this); } }
/// <summary>Gets the signed area of the "major" contour (the first one). /// It's signed as this can identify the winding order.</summary> public float GetSignedArea() { float sum = 0f; VectorPoint current = FirstPathNode; while (current != null) { VectorPoint next = current.Next; if (next == null) { // We're done! break; } if (next.HasLine) { sum += (next.Y + current.Y) * (next.X - current.X); } current = next; } return(sum / 2f); }
/// <summary>Gets the nearest node in the given section of this shape to the given point.</summary> public VectorPoint Nearest(float x, float y, VectorPoint from, VectorPoint to) { VectorPoint nearest = null; float distance = float.MaxValue; // For each side, check if it is to the left of our point. if it is, flip contained. VectorPoint current = from; while (current != null) { float dx = current.X - x; float dy = current.Y - y; float dist = dx * dx + dy * dy; if (dist < distance) { nearest = current; distance = dist; } if (current == to) { // All done. break; } current = current.Next; } return(nearest); }
/// <summary>Finds all the separate contours in this path.</summary> public List <PathSegment> GetContours() { // All the first nodes of each contour: List <PathSegment> contours = new List <PathSegment>(); PathSegment currentContour = null; // Find all the contours: VectorPoint current = FirstPathNode; while (current != null) { if (currentContour == null) { // Create the contour: currentContour = new PathSegment(current, this); } if (current.Next == null || !current.Next.HasLine) { // Ensure it's closed: current.IsClose = true; currentContour.Last = current; contours.Add(currentContour); currentContour = null; } current = current.Next; } return(contours); }
public int GetVertexCount(float accuracy) { int vertCount = 0; // Next, we must consider all extra points caused by curves: VectorPoint current = FirstPathNode; while (current != null) { if (current.IsCurve) { // It's a curve - get the line length: VectorLine line = current as VectorLine; float length = line.Length; // And just add on extra points: vertCount += (int)(length / accuracy); } if (!current.IsClose) { vertCount++; } // Hop to the next one: current = current.Next; } return(vertCount); }
/// <summary>Recalculates the minimum values and width/height of this path, taking curves into account.</summary> public void RecalculateBounds() { if (FirstPathNode == null) { Width = 0f; Height = 0f; MinY = 0f; MinX = 0f; return; } // Our temp boundaries: MinX = float.MaxValue; MinY = float.MaxValue; // We'll be using width/height temporarily as max: Width = float.MinValue; Height = float.MinValue; VectorPoint current = FirstPathNode; while (current != null) { // Recalc bounds: current.RecalculateBounds(this); // Hop to the next one: current = current.Next; } // Remove min values from width/height: Width -= MinX; Height -= MinY; }
/// <summary>Removes this segment from the parent path.</summary> public void Remove() { // Get previous: VectorPoint previous = First.Previous; // Get next: VectorPoint next = Last.Next; if (previous == null) { Path.FirstPathNode = next; } else { previous.Next = next; } if (next == null) { Path.LatestPathNode = previous; } else { next.Previous = previous; } }
/// <summary>Replaces this point with another.</summary> public void ReplaceWith(VectorPoint replacement, VectorPath path) { if (replacement == null) { return; } // Update next/prev: replacement.Next = Next; replacement.Previous = Previous; replacement.IsClose = IsClose; if (Next == null) { path.LatestPathNode = replacement; } else { Next.Previous = replacement; } if (Previous == null) { path.FirstPathNode = replacement; } else { Previous.Next = replacement; } }
public void Transform(Matrix4x4 mat) { VectorPoint current = FirstPathNode; while (current != null) { current.Transform(mat); current = current.Next; } }
/// <summary>"Simplifies" the curve values ensuring that it's possible to offset the parts of the path. /// Used by the path stroke system.</summary> public void SimplifyCurve() { VectorPoint current = FirstPathNode; while (current != null) { if (current.IsCurve) { CurveLinePoint clp = current as CurveLinePoint; if (clp != null) { // Simplify: clp.SimplifyCurve(this); } else { // Convert a QLP to a CLP: QuadLinePoint qlp = current as QuadLinePoint; if (qlp != null) { // Create but with both control points being the same: clp = new CurveLinePoint(qlp.X, qlp.Y); clp.Control1X = qlp.Control1X; clp.Control1Y = qlp.Control1Y; clp.Control2X = qlp.Control1X; clp.Control2Y = qlp.Control1Y; clp.IsClose = qlp.IsClose; // Replace the node now: if (qlp.Previous == null) { FirstPathNode = clp; } else { qlp.Previous.Next = clp; } if (qlp.Next == null) { LatestPathNode = clp; } else { qlp.Next.Previous = clp; } } } } current = current.Next; } }
/// <summary>Bounds the points in this path to being real workable numbers. /// NaN and infinities are eliminated.</summary> public void BoundToReal() { // Eliminate NaN/ infinities: VectorPoint point = FirstPathNode; while (point != null) { point.BoundToReal(); point = point.Next; } }
/// <summary>Selects the point at the given index of this path.</summary> public VectorPoint SelectPoint(int index) { VectorPoint current = FirstPathNode; while (index != 0 && current != null) { index--; current = current.Next; } return(current); }
/// <summary>Multiply the normals of this path by the given value.</summary> public void MultiplyNormals(float by) { VectorPoint current = FirstPathNode; while (current != null) { current.MultiplyNormals(by); // Hop to the next one: current = current.Next; } }
/// <summary>Sheers this path. Note that it's assumed to be at most 1 unit tall.</summary> public void Sheer(float by) { VectorPoint current = FirstPathNode; while (current != null) { current.Sheer(by); // Hop to the next one: current = current.Next; } }
/// <summary>Advances this sampler by the given amount.</summary> public virtual void Advance(float by, bool readValue) { // Update the distance stepped: DistanceStepped += by; if (Current == null) { return; } bool changed = false; while (Current.Next != null) { // Get the current x point: float x = Current.Next.X; if (DistanceStepped < x) { break; } // Hop to the next one: Current = Current.Next; changed = true; } if (readValue) { if (Current.Next == null) { // Apply current - This is where clamp comes from: CurrentValue = Current.Y; return; } if (changed) { Current.Next.SetupSampler(this); } // What's the difference between next and this? float deltaX = Current.Next.X - Current.X; // 0-1 progress factor: float progress = (DistanceStepped - Current.X) / deltaX; CurrentValue = Current.Next.SampleMapped(this, progress); } }
/// <summary>Extrudes this path along it's normals. Used to e.g. make something bold. Assumes one or more closed loops and that RecalculateNormals has been called.</summary> public void Extrude(float by) { VectorPoint current = FirstPathNode; while (current != null) { // Pull it out: current.Extrude(by); // Hop to the next one: current = current.Next; } }
/// <summary>Copies a section of this path. /// Note that if p2 is before p1, it will safely loop over a closed node. /// </summary> public VectorPath CopySection(VectorPoint p1, float c1, VectorPoint p2, float c2) { // Split the line p1 at c1. // Split the line p2 at c2. // The section between these two nodes is the part we want. // Create the path: VectorPath path = new VectorPath(); if (p1 == null) { p1 = FirstPathNode; c1 = 0f; } // Add the first node: path.AddPathNode(p1.PointAt(c1, true)); VectorPoint current = p1.Next; while (current != p2 && current != null) { if (current == p1) { // This occurs when p1 and p2 are not in the same shape. throw new Exception("Section start and end are not from the same path."); } // Add a copy: path.AddPathNode(current.Copy()); // Go to next: current = current.Next; if (current == null && p2 != null) { // Need to loop back around unless p2 is null. // Notice that we actively skip the moveTo itself as // current is in the same location. current = path.FirstPathNode.Next; } } if (current == p2 && p2 != null) { // Add it: path.AddPathNode(p2.PointAt(c2, false)); } return(path); }
/// <summary>Recomputes path node count.</summary> public void CountNodes() { PathNodeCount = 0; VectorPoint current = FirstPathNode; while (current != null) { // Add: PathNodeCount++; // Hop to the next one: current = current.Next; } }
public override string ToString() { string text = ""; VectorPoint current = FirstPathNode; while (current != null) { text += current.ToString() + "\r\n"; // Hop to the next one: current = current.Next; } return(text); }
/// <summary>Scales this path by the given value.</summary> public void Move(float byX, float byY) { VectorPoint current = FirstPathNode; while (current != null) { current.Move(byX, byY); // Hop to the next one: current = current.Next; } // Move bounds: MinX += byX; MinY += byY; }
/// <summary>Gets the signed area of the "major" contour (the first one). /// It's signed as this can identify the winding order.</summary> public float GetSignedArea() { float sum = 0f; VectorPoint current = FirstPathNode; while (current != null) { if (current.HasLine) { sum += current.SignedArea(); } current = current.Next; } return(sum / 2f); }
/// <summary>Scales this path by the given value.</summary> public void Scale(float by) { VectorPoint current = FirstPathNode; while (current != null) { current.Multiply(by); // Hop to the next one: current = current.Next; } // Scale the dimensions: MinX *= by; MinY *= by; Width *= by; Height *= by; }
/// <summary>Adds the given node to the start of the path. Must be a moveTo unless it's a temp thing. /// See AddPathNode to add to the end.</summary> public void AddPathNodeStart(VectorPoint point) { PathNodeCount++; if (FirstPathNode == null) { if (point.Unloaded) { FirstPathNode = LatestPathNode = point; } else { MoveToPoint move = point as MoveToPoint; if (move == null) { // Add a blank MoveTo - this means that moveTo's are always the close nodes. move = new MoveToPoint(0f, 0f); FirstPathNode = LatestPathNode = move; CloseNode = move; PathNodeCount++; point.Previous = move; LatestPathNode = move.Next = point; } else { FirstPathNode = LatestPathNode = point; CloseNode = move; } } } else { // Hook it onto the start: point.Previous = null; point.Next = FirstPathNode; FirstPathNode.Previous = point; FirstPathNode = point; } }
/// <summary>Resets the stepping through this sampler.</summary> public void Reset() { Current = Path.FirstPathNode; DistanceStepped = 0f; // Write the value: if (Current != null) { CurrentValue = Current.Y; if (Current.Next != null) { Current.Next.SetupSampler(this); } } else { CurrentValue = 0f; } }
/// <summary>Remove a point. /// Just directly removes the point from the linked list unlike RemoveVisually.</summary> public void Remove(VectorPoint point) { if (point.Previous == null) { FirstPathNode = point.Next; } else { point.Previous.Next = point.Next; } if (point.Next == null) { LatestPathNode = point.Previous; } else { point.Next.Previous = point.Previous; } }
/// <summary>Copies this vector path into the given one.</summary> public void CopyInto(VectorPath path) { VectorPoint point = FirstPathNode; while (point != null) { VectorPoint copiedPoint = point.Copy(); path.AddPathNode(copiedPoint); // Copy close status: if (point.IsClose) { copiedPoint.IsClose = true; path.CloseNode.ClosePoint = copiedPoint; } point = point.Next; } }
/// <summary>The length of this path.</summary> public float Length() { float length = 0f; VectorPoint current = FirstPathNode; while (current != null) { if (current.HasLine) { VectorLine line = current as VectorLine; length += line.Length; } // Hop to the next one: current = current.Next; } return(length); }
/// <summary>Axis flip.</summary> public void Flip() { VectorPoint current = FirstPathNode; while (current != null) { current.Flip(); // Hop to the next one: current = current.Next; } float x = Width; Width = Height; Height = x; x = MinX; MinX = MinY; MinY = x; }
/// <summary>Adds the given path onto the end of this one.</summary> public void Append(VectorPath path) { if (LatestPathNode == null) { // This path just becomes the same as the given one: FirstPathNode = path.FirstPathNode; LatestPathNode = path.LatestPathNode; PathNodeCount = path.PathNodeCount; CloseNode = path.CloseNode; } else { // Add the first node: AddPathNode(path.FirstPathNode); LatestPathNode = path.LatestPathNode; // -1 because Add already added the first node. PathNodeCount += path.PathNodeCount - 1; CloseNode = path.CloseNode; } }
public VectorPoint GetShapeEnd() { if (Next == null) { return(this); } VectorPoint current = Next; while (current != null) { // Nothing after it or the next one is a moveto: if (current.Next == null || !current.Next.HasLine) { return(current); } current = current.Next; } // This is actually unreachable: return(null); }
/// <summary>Does the given section of this path contain the given point?</summary> public bool Contains(float x,float y,VectorPoint from,VectorPoint to){ bool contained=false; // For each side, check if it is to the left of our point. if it is, flip contained. VectorPoint current=from; while(current!=null){ VectorPoint pointB=(current==from)?to:current.Previous; // Figure out the bounding box of the line. // We're going to see if the point is outside it - if so, skip. float minX=(current.X<pointB.X)?current.X:pointB.X; // Point is to the left of tbe bounding box - ignore. if(minX>x){ goto Next; } float maxX=(current.X>pointB.X)?current.X:pointB.X; // Point is to the right of this lines bounding box - ignore. // We do an inclusive ignore here as the line attached to this one might include it too. if(maxX<=x){ goto Next; } float minY=(current.Y<pointB.Y)?current.Y:pointB.Y; // Point is below this lines bounding box - ignore. if(minY>y){ goto Next; } // Special case if the point is above. float maxY=(current.Y>pointB.Y)?current.Y:pointB.Y; // We do an inclusive check here as the line attached to this one might include it too. if(maxY<=y){ //The point is above for sure. contained=!contained; goto Next; } // It's sloping. What side of the line are we on? If we're on the right, the line is to the left. float gradient=(pointB.Y-current.Y)/(pointB.X-current.X); float c=current.Y-(gradient*current.X); // y<=mx+c means we're on the right, or on the line. if(((gradient*x)+c)<=y){ contained=!contained; } if(current==to){ // All done. break; } Next: current=current.Next; } return contained; }
/// <summary>Gets the nearest node in the given section of this shape to the given point.</summary> public VectorPoint Nearest(float x,float y,VectorPoint from,VectorPoint to){ VectorPoint nearest=null; float distance=float.MaxValue; // For each side, check if it is to the left of our point. if it is, flip contained. VectorPoint current=from; while(current!=null){ float dx=current.X-x; float dy=current.Y-y; float dist=dx*dx + dy*dy; if(dist<distance){ nearest=current; distance=dist; } if(current==to){ // All done. break; } current=current.Next; } return nearest; }
//--------------------------------------