mirror of
https://github.com/doublecmd/doublecmd.git
synced 2026-06-21 09:58:13 +00:00
3689 lines
112 KiB
ObjectPascal
3689 lines
112 KiB
ObjectPascal
unit Img32.Vector;
|
|
|
|
(*******************************************************************************
|
|
* Author : Angus Johnson *
|
|
* Version : 4.3 *
|
|
* Date : 27 September 2022 *
|
|
* Website : http://www.angusj.com *
|
|
* Copyright : Angus Johnson 2019-2022 *
|
|
* *
|
|
* Purpose : Vector drawing for TImage32 *
|
|
* *
|
|
* License : Use, modification & distribution is subject to *
|
|
* Boost Software License Ver 1 *
|
|
* http://www.boost.org/LICENSE_1_0.txt *
|
|
*******************************************************************************)
|
|
|
|
interface
|
|
|
|
{$I Img32.inc}
|
|
|
|
uses
|
|
SysUtils, Classes, Math, Types, Img32;
|
|
|
|
type
|
|
TArrowStyle = (asNone, asSimple, asFancy, asDiamond, asCircle, asTail);
|
|
TJoinStyle = (jsAuto, jsSquare, jsMiter, jsRound);
|
|
TEndStyle = (esPolygon = 0, esClosed = 0, esButt, esSquare, esRound);
|
|
TPathEnd = (peStart, peEnd, peBothEnds);
|
|
TSplineType = (stQuadratic, stCubic);
|
|
TFillRule = (frEvenOdd, frNonZero, frPositive, frNegative);
|
|
TImg32FillRule = TFillRule; //useful whenever there's ambiguity with Clipper
|
|
|
|
TSizeD = {$IFDEF RECORD_METHODS} record {$ELSE} object {$ENDIF}
|
|
cx : double;
|
|
cy : double;
|
|
function average: double;
|
|
property Width: Double read cx write cx;
|
|
property Height: Double read cy write cy;
|
|
end;
|
|
|
|
TRectWH = {$IFDEF RECORD_METHODS} record {$ELSE} object {$ENDIF}
|
|
public
|
|
Left, Top, Width, Height: double;
|
|
function IsEmpty: Boolean;
|
|
function IsValid: Boolean;
|
|
function Right: double;
|
|
function Bottom: double;
|
|
function Contains(const Pt: TPoint): Boolean; overload;
|
|
function Contains(const Pt: TPointD): Boolean; overload;
|
|
function MidPoint: TPointD;
|
|
function RectD: TRectD;
|
|
function Rect: TRect;
|
|
end;
|
|
|
|
function RectWH(left, top, width, height: integer): TRectWH; overload;
|
|
function RectWH(left, top, width, height: double ): TRectWH; overload;
|
|
function RectWH(const rec: TRectD): TRectWH; overload;
|
|
|
|
//InflateRect: missing in Delphi 7
|
|
procedure InflateRect(var rec: TRect; dx, dy: integer); overload;
|
|
procedure InflateRect(var rec: TRectD; dx, dy: double); overload;
|
|
|
|
function NormalizeRect(var rect: TRect): Boolean;
|
|
|
|
function PrePendPoint(const pt: TPointD; const p: TPathD): TPathD;
|
|
function PrePendPoints(const pt1, pt2: TPointD; const p: TPathD): TPathD;
|
|
|
|
function Rectangle(const rec: TRect): TPathD; overload;
|
|
function Rectangle(const rec: TRectD): TPathD; overload;
|
|
function Rectangle(l, t, r, b: double): TPathD; overload;
|
|
|
|
function RoundRect(const rec: TRect; radius: integer): TPathD; overload;
|
|
function RoundRect(const rec: TRectD; radius: double): TPathD; overload;
|
|
function RoundRect(const rec: TRect; radius: TPoint): TPathD; overload;
|
|
function RoundRect(const rec: TRectD; radius: TPointD): TPathD; overload;
|
|
|
|
function Ellipse(const rec: TRect; steps: integer = 0): TPathD; overload;
|
|
function Ellipse(const rec: TRectD; steps: integer = 0): TPathD; overload;
|
|
function Ellipse(const rec: TRectD; pendingScale: double): TPathD; overload;
|
|
|
|
function RotatedEllipse(const rec: TRectD; angle: double; steps: integer = 0): TPathD; overload;
|
|
function RotatedEllipse(const rec: TRectD; angle: double; pendingScale: double): TPathD; overload;
|
|
|
|
function AngleToEllipticalAngle(const ellRec: TRectD; angle: double): double;
|
|
|
|
function EllipticalAngleToAngle(const ellRec: TRectD; angle: double): double;
|
|
|
|
function Circle(const pt: TPoint; radius: double): TPathD; overload;
|
|
function Circle(const pt: TPointD; radius: double): TPathD; overload;
|
|
function Circle(const pt: TPointD; radius: double; pendingScale: double): TPathD; overload;
|
|
|
|
function Star(const rec: TRectD; points: integer; indentFrac: double = 0.4): TPathD; overload;
|
|
function Star(const focalPt: TPointD;
|
|
innerRadius, outerRadius: double; points: integer): TPathD; overload;
|
|
|
|
function Arc(const rec: TRectD;
|
|
startAngle, endAngle: double; scale: double = 0): TPathD;
|
|
|
|
function Pie(const rec: TRectD;
|
|
StartAngle, EndAngle: double; scale: double = 0): TPathD;
|
|
|
|
function FlattenQBezier(const pt1, pt2, pt3: TPointD;
|
|
tolerance: double = 0.0): TPathD; overload;
|
|
function FlattenQBezier(const pts: TPathD;
|
|
tolerance: double = 0.0): TPathD; overload;
|
|
function FlattenQBezier(const firstPt: TPointD; const pts: TPathD;
|
|
tolerance: double = 0.0): TPathD; overload;
|
|
|
|
function GetPointInQuadBezier(const a,b,c: TPointD; t: double): TPointD;
|
|
|
|
function FlattenCBezier(const pt1, pt2, pt3, pt4: TPointD;
|
|
tolerance: double = 0.0): TPathD; overload;
|
|
function FlattenCBezier(const pts: TPathD;
|
|
tolerance: double = 0.0): TPathD; overload;
|
|
function FlattenCBezier(const firstPt: TPointD; const pts: TPathD;
|
|
tolerance: double = 0.0): TPathD; overload;
|
|
|
|
function GetPointInCubicBezier(const a,b,c,d: TPointD; t: double): TPointD;
|
|
|
|
//FlattenCSpline: Approximates the 'S' command inside the 'd' property of an
|
|
//SVG path. (See https://www.w3.org/TR/SVG/paths.html#DProperty)
|
|
function FlattenCSpline(const pts: TPathD;
|
|
tolerance: double = 0.0): TPathD; overload;
|
|
function FlattenCSpline(const priorCtrlPt, startPt: TPointD;
|
|
const pts: TPathD; tolerance: double = 0.0): TPathD; overload;
|
|
|
|
//FlattenQSpline: Approximates the 'T' command inside the 'd' property of an
|
|
//SVG path. (See https://www.w3.org/TR/SVG/paths.html#DProperty)
|
|
function FlattenQSpline(const pts: TPathD;
|
|
tolerance: double = 0.0): TPathD; overload;
|
|
function FlattenQSpline(const priorCtrlPt, startPt: TPointD;
|
|
const pts: TPathD; tolerance: double = 0.0): TPathD; overload;
|
|
|
|
//ArrowHead: The ctrlPt's only function is to control the angle of the arrow.
|
|
function ArrowHead(const arrowTip, ctrlPt: TPointD; size: double;
|
|
arrowStyle: TArrowStyle): TPathD;
|
|
|
|
function GetDefaultArrowHeadSize(lineWidth: double): double;
|
|
|
|
procedure AdjustPoint(var pt: TPointD;
|
|
const referencePt: TPointD; delta: double);
|
|
|
|
function ShortenPath(const path: TPathD;
|
|
pathEnd: TPathEnd; amount: double): TPathD;
|
|
|
|
//GetDashPath: Returns a polyline (not polygons)
|
|
function GetDashedPath(const path: TPathD;
|
|
closed: Boolean; const pattern: TArrayOfInteger;
|
|
patternOffset: PDouble): TPathsD;
|
|
|
|
function GetDashedOutLine(const path: TPathD;
|
|
closed: Boolean; const pattern: TArrayOfInteger;
|
|
patternOffset: PDouble; lineWidth: double;
|
|
joinStyle: TJoinStyle; endStyle: TEndStyle): TPathsD;
|
|
|
|
function OffsetPoint(const pt: TPoint; dx, dy: integer): TPoint; overload;
|
|
function OffsetPoint(const pt: TPointD; dx, dy: double): TPointD; overload;
|
|
|
|
function OffsetPath(const path: TPathD;
|
|
dx, dy: double): TPathD; overload;
|
|
function OffsetPath(const paths: TPathsD;
|
|
dx, dy: double): TPathsD; overload;
|
|
function OffsetPath(const ppp: TArrayOfPathsD;
|
|
dx, dy: double): TArrayOfPathsD; overload;
|
|
|
|
function Paths(const path: TPathD): TPathsD;
|
|
{$IFDEF INLINING} inline; {$ENDIF}
|
|
|
|
//CopyPath: note that only dynamic string arrays are copy-on-write
|
|
function CopyPath(const path: TPathD): TPathD;
|
|
{$IFDEF INLINING} inline; {$ENDIF}
|
|
function CopyPaths(const paths: TPathsD): TPathsD;
|
|
|
|
function ScalePoint(const pt: TPointD; scale: double): TPointD; overload;
|
|
{$IFDEF INLINING} inline; {$ENDIF}
|
|
function ScalePoint(const pt: TPointD; sx, sy: double): TPointD; overload;
|
|
{$IFDEF INLINING} inline; {$ENDIF}
|
|
function ScalePath(const path: TPathD;
|
|
sx, sy: double): TPathD; overload;
|
|
|
|
function ScalePath(const path: TPathD;
|
|
scale: double): TPathD; overload;
|
|
function ScalePath(const paths: TPathsD;
|
|
sx, sy: double): TPathsD; overload;
|
|
function ScalePath(const paths: TPathsD;
|
|
scale: double): TPathsD; overload;
|
|
|
|
function ScaleRect(const rec: TRect; scale: double): TRect; overload;
|
|
function ScaleRect(const rec: TRectD; scale: double): TRectD; overload;
|
|
function ScaleRect(const rec: TRect; sx, sy: double): TRect; overload;
|
|
function ScaleRect(const rec: TRectD; sx, sy: double): TRectD; overload;
|
|
|
|
function ReversePath(const path: TPathD): TPathD; overload;
|
|
function ReversePath(const paths: TPathsD): TPathsD; overload;
|
|
|
|
function OpenPathToFlatPolygon(const path: TPathD): TPathD;
|
|
|
|
procedure AppendPoint(var path: TPathD; const extra: TPointD);
|
|
|
|
procedure AppendPath(var path: TPathD; const pt: TPointD); overload;
|
|
procedure AppendPath(var path1: TPathD; const path2: TPathD); overload;
|
|
procedure AppendPath(var paths: TPathsD; const extra: TPathD); overload;
|
|
procedure AppendPath(var paths: TPathsD; const extra: TPathsD); overload;
|
|
procedure AppendPath(var ppp: TArrayOfPathsD; const extra: TPathsD); overload;
|
|
|
|
function GetAngle(const origin, pt: TPoint): double; overload;
|
|
function GetAngle(const origin, pt: TPointD): double; overload;
|
|
function GetAngle(const a, b, c: TPoint): double; overload;
|
|
function GetAngle(const a, b, c: TPointD): double; overload;
|
|
|
|
procedure GetSinCos(angle: double; out sinA, cosA: double);
|
|
|
|
function GetPointAtAngleAndDist(const origin: TPointD;
|
|
angle, distance: double): TPointD;
|
|
|
|
function IntersectPoint(const ln1a, ln1b, ln2a, ln2b: TPointD): TPointD; overload;
|
|
function IntersectPoint(const ln1a, ln1b, ln2a, ln2b: TPointD; out ip: TPointD): Boolean; overload;
|
|
|
|
function SegmentIntersectPt(const ln1a, ln1b, ln2a, ln2b: TPointD): TPointD;
|
|
function SegmentsIntersect(const ln1a, ln1b, ln2a, ln2b: TPointD;
|
|
out ip: TPointD): Boolean;
|
|
|
|
procedure RotatePoint(var pt: TPointD;
|
|
const focalPoint: TPointD; sinA, cosA: double); overload;
|
|
procedure RotatePoint(var pt: TPointD;
|
|
const focalPoint: TPointD; angleRad: double); overload;
|
|
|
|
function RotatePath(const path: TPathD;
|
|
const focalPoint: TPointD; angleRads: double): TPathD; overload;
|
|
function RotatePath(const paths: TPathsD;
|
|
const focalPoint: TPointD; angleRads: double): TPathsD; overload;
|
|
|
|
//function MakePath(const pts: array of integer): TPathD; overload;
|
|
function MakePath(const pts: array of double): TPathD; overload;
|
|
|
|
function GetBounds(const path: TPathD): TRect; overload;
|
|
function GetBounds(const paths: TPathsD): TRect; overload;
|
|
|
|
function GetBoundsD(const path: TPathD): TRectD; overload;
|
|
function GetBoundsD(const paths: TPathsD): TRectD; overload;
|
|
|
|
function GetRotatedRectBounds(const rec: TRect; angle: double): TRect; overload;
|
|
function GetRotatedRectBounds(const rec: TRectD; angle: double): TRectD; overload;
|
|
|
|
function Rect(const recD: TRectD): TRect; overload;
|
|
function Rect(const left,top,right,bottom: integer): TRect; overload;
|
|
|
|
function PtInRect(const rec: TRectD; const pt: TPointD): Boolean; overload;
|
|
|
|
function Size(cx, cy: integer): TSize;
|
|
function SizeD(cx, cy: double): TSizeD;
|
|
|
|
function IsClockwise(const path: TPathD): Boolean;
|
|
|
|
function Area(const path: TPathD): Double; overload;
|
|
|
|
function RectsEqual(const rec1, rec2: TRect): Boolean;
|
|
|
|
procedure OffsetRect(var rec: TRectD; dx, dy: double); overload;
|
|
|
|
function MakeSquare(rec: TRect): TRect;
|
|
|
|
function IsValid(value: integer): Boolean; overload;
|
|
function IsValid(value: double): Boolean; overload;
|
|
function IsValid(const pt: TPoint): Boolean; overload;
|
|
function IsValid(const pt: TPointD): Boolean; overload;
|
|
function IsValid(const rec: TRect): Boolean; overload;
|
|
|
|
function Point(X,Y: Integer): TPoint; overload;
|
|
function Point(const pt: TPointD): TPoint; overload;
|
|
|
|
function PointsEqual(const pt1, pt2: TPointD): Boolean; overload;
|
|
{$IFDEF INLINING} inline; {$ENDIF}
|
|
|
|
function PointsNearEqual(const pt1, pt2: TPoint;
|
|
dist: integer): Boolean; overload;
|
|
function PointsNearEqual(const pt1, pt2: TPointD;
|
|
distSqrd: double): Boolean; overload;
|
|
{$IFDEF INLINING} inline; {$ENDIF}
|
|
|
|
function StripNearDuplicates(const path: TPathD;
|
|
minDist: double; isClosedPath: Boolean): TPathD; overload;
|
|
function StripNearDuplicates(const paths: TPathsD;
|
|
minLength: double; isClosedPaths: Boolean): TPathsD; overload;
|
|
|
|
function MidPoint(const rec: TRect): TPoint; overload;
|
|
function MidPoint(const rec: TRectD): TPointD; overload;
|
|
function MidPoint(const pt1, pt2: TPoint): TPoint; overload;
|
|
function MidPoint(const pt1, pt2: TPointD): TPointD; overload;
|
|
|
|
function Average(val1, val2: integer): integer; overload;
|
|
function Average(val1, val2: double): double; overload;
|
|
|
|
function ReflectPoint(const pt, pivot: TPointD): TPointD;
|
|
{$IFDEF INLINING} inline; {$ENDIF}
|
|
|
|
function RectsOverlap(const rec1, rec2: TRect): Boolean;
|
|
|
|
function IsSameRect(const rec1, rec2: TRect): Boolean;
|
|
|
|
function RectsIntersect(const rec1, rec2: TRect): Boolean; overload;
|
|
function RectsIntersect(const rec1, rec2: TRectD): Boolean; overload;
|
|
function IntersectRect(const rec1, rec2: TRectD): TRectD; overload;
|
|
|
|
//UnionRect: this behaves differently to types.UnionRect
|
|
//in that if either parameter is empty the other parameter is returned
|
|
function UnionRect(const rec1, rec2: TRect): TRect; overload;
|
|
function UnionRect(const rec1, rec2: TRectD): TRectD; overload;
|
|
|
|
//these 2 functions are only needed to support older versions of Delphi
|
|
function MakeArrayOfInteger(const ints: array of integer): TArrayOfInteger;
|
|
function MakeArrayOfDouble(const doubles: array of double): TArrayOfDouble;
|
|
|
|
function CrossProduct(const vector1, vector2: TPointD): double; overload;
|
|
{$IFDEF INLINING} inline; {$ENDIF}
|
|
function CrossProduct(const pt1, pt2, pt3: TPointD): double; overload;
|
|
{$IFDEF INLINING} inline; {$ENDIF}
|
|
function CrossProduct(const pt1, pt2, pt3, pt4: TPointD): double; overload;
|
|
{$IFDEF INLINING} inline; {$ENDIF}
|
|
|
|
function DotProduct(const vector1, vector2: TPointD): double; overload;
|
|
{$IFDEF INLINING} inline; {$ENDIF}
|
|
function DotProduct(const pt1, pt2, pt3: TPointD): double; overload;
|
|
{$IFDEF INLINING} inline; {$ENDIF}
|
|
|
|
function TurnsLeft(const pt1, pt2, pt3: TPointD): boolean;
|
|
{$IFDEF INLINING} inline; {$ENDIF}
|
|
function TurnsRight(const pt1, pt2, pt3: TPointD): boolean;
|
|
{$IFDEF INLINING} inline; {$ENDIF}
|
|
|
|
function IsPathConvex(const path: TPathD): Boolean;
|
|
|
|
function NormalizeVector(const vec: TPointD): TPointD;
|
|
{$IFDEF INLINING} inline; {$ENDIF}
|
|
|
|
//GetUnitVector: Used internally
|
|
function GetUnitVector(const pt1, pt2: TPointD): TPointD;
|
|
|
|
//GetUnitNormal: Used internally
|
|
function GetUnitNormal(const pt1, pt2: TPointD): TPointD; overload;
|
|
{$IFDEF INLINING} inline; {$ENDIF}
|
|
function GetUnitNormal(const pt1, pt2: TPointD; out norm: TPointD): Boolean; overload;
|
|
{$IFDEF INLINING} inline; {$ENDIF}
|
|
|
|
function GetAvgUnitVector(const vec1, vec2: TPointD): TPointD;
|
|
{$IFDEF INLINING} inline; {$ENDIF}
|
|
|
|
//GetVectors: Used internally
|
|
function GetVectors(const path: TPathD): TPathD;
|
|
//GetNormals: Used internally
|
|
|
|
function GetNormals(const path: TPathD): TPathD;
|
|
|
|
//DistanceSqrd: Used internally
|
|
function DistanceSqrd(const pt1, pt2: TPoint): double; overload;
|
|
{$IFDEF INLINE} inline; {$ENDIF}
|
|
//DistanceSqrd: Used internally
|
|
function DistanceSqrd(const pt1, pt2: TPointD): double; overload;
|
|
{$IFDEF INLINE} inline; {$ENDIF}
|
|
|
|
function Distance(const pt1, pt2: TPoint): double; overload;
|
|
{$IFDEF INLINE} inline; {$ENDIF}
|
|
function Distance(const pt1, pt2: TPointD): double; overload;
|
|
{$IFDEF INLINE} inline; {$ENDIF}
|
|
function Distance(const path: TPathD; stopAt: integer = 0): double; overload;
|
|
|
|
function GetDistances(const path: TPathD): TArrayOfDouble;
|
|
|
|
function GetCumulativeDistances(const path: TPathD): TArrayOfDouble;
|
|
|
|
function PerpendicularDistSqrd(const pt, line1, line2: TPointD): double;
|
|
|
|
function PointInPolygon(const pt: TPointD;
|
|
const polygon: TPathD; fillRule: TFillRule): Boolean;
|
|
|
|
function PointInPolygons(const pt: TPointD;
|
|
const polygons: TPathsD; fillRule: TFillRule): Boolean;
|
|
|
|
function PerpendicularDist(const pt, line1, line2: TPointD): double;
|
|
|
|
function ClosestPointOnLine(const pt, linePt1, linePt2: TPointD): TPointD;
|
|
|
|
function ClosestPointOnSegment(const pt, segPt1, segPt2: TPointD): TPointD;
|
|
|
|
function IsPointInEllipse(const ellipseRec: TRect; const pt: TPoint): Boolean;
|
|
|
|
//GetIntersectsEllipseAndLine: Gets the intersection of an ellipse and
|
|
//a line. The function result = true when the line either touches
|
|
//tangentially or passes through the ellipse. If the line touches
|
|
//tangentially, the coordintates returned in pt1 and pt2 will match.
|
|
function GetLineEllipseIntersects(const ellipseRec: TRect;
|
|
var linePt1, linePt2: TPointD): Boolean;
|
|
|
|
function GetPtOnEllipseFromAngle(const ellipseRect: TRectD; angle: double): TPointD;
|
|
|
|
function GetPtOnRotatedEllipseFromAngle(const ellipseRect: TRectD;
|
|
ellipseRotAngle, angle: double): TPointD;
|
|
|
|
function GetEllipticalAngleFromPoint(const ellipseRect: TRectD;
|
|
const pt: TPointD): double;
|
|
|
|
function GetRotatedEllipticalAngleFromPoint(const ellipseRect: TRectD;
|
|
ellipseRotAngle: double; pt: TPointD): double;
|
|
|
|
function GetClosestPtOnRotatedEllipse(const ellipseRect: TRectD;
|
|
ellipseRotation: double; const pt: TPointD): TPointD;
|
|
|
|
function Outline(const line: TPathD; lineWidth: double;
|
|
joinStyle: TJoinStyle; endStyle: TEndStyle;
|
|
miterLimOrRndScale: double = 0): TPathsD; overload;
|
|
function Outline(const lines: TPathsD; lineWidth: double;
|
|
joinStyle: TJoinStyle; endStyle: TEndStyle;
|
|
miterLimOrRndScale: double = 0): TPathsD; overload;
|
|
|
|
//Grow: Offsets path by 'delta' (positive is away from the left of the path).
|
|
//With a positive delta, clockwise paths will expand and counter-clockwise
|
|
//ones will contract. The reverse happens with negative deltas.
|
|
function Grow(const path, normals: TPathD; delta: double; joinStyle: TJoinStyle;
|
|
miterLim: double; isOpen: Boolean = false): TPathD;
|
|
|
|
function ValueAlmostZero(val: double; epsilon: double = 0.001): Boolean;
|
|
function ValueAlmostOne(val: double; epsilon: double = 0.001): Boolean;
|
|
const
|
|
Invalid = -MaxInt;
|
|
InvalidD = -Infinity;
|
|
NullPoint : TPoint = (X: 0; Y: 0);
|
|
NullPointD : TPointD = (X: 0; Y: 0);
|
|
InvalidPoint : TPoint = (X: -MaxInt; Y: -MaxInt);
|
|
InvalidPointD : TPointD = (X: -Infinity; Y: -Infinity);
|
|
NullRect : TRect = (left: 0; top: 0; right: 0; Bottom: 0);
|
|
NullRectD : TRectD = (left: 0; top: 0; right: 0; Bottom: 0);
|
|
InvalidRect : TRect = (left: MaxInt; top: MaxInt; right: 0; Bottom: 0);
|
|
BezierTolerance: double = 0.25;
|
|
var
|
|
//AutoWidthThreshold: When JoinStyle = jsAuto, this is the threshold at
|
|
//which line joins will be rounded instead of squared. With wider strokes,
|
|
//rounded joins generally look better, but as rounding is more complex it
|
|
//also requries more processing and hence is slower to execute.
|
|
AutoWidthThreshold: double = 5.0;
|
|
//When lines are too narrow, they become too faint to sensibly draw
|
|
MinStrokeWidth: double = 0.5;
|
|
//Miter limit avoids excessive spikes when line offsetting
|
|
DefaultMiterLimit: double = 4.0;
|
|
|
|
resourcestring
|
|
rsInvalidMatrix = 'Invalid matrix.'; //nb: always start with IdentityMatrix
|
|
|
|
implementation
|
|
|
|
resourcestring
|
|
rsInvalidQBezier = 'Invalid number of control points for a QBezier';
|
|
rsInvalidCBezier = 'Invalid number of control points for a CBezier';
|
|
|
|
const
|
|
BuffSize = 64;
|
|
|
|
//------------------------------------------------------------------------------
|
|
// TSizeD
|
|
//------------------------------------------------------------------------------
|
|
|
|
function TSizeD.average: double;
|
|
begin
|
|
Result := (cx + cy) * 0.5;
|
|
end;
|
|
|
|
//------------------------------------------------------------------------------
|
|
// TRectWH record/object.
|
|
//------------------------------------------------------------------------------
|
|
|
|
function TRectWH.IsEmpty: Boolean;
|
|
begin
|
|
Result := (Width <= 0) or (Height <= 0);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function TRectWH.IsValid: Boolean;
|
|
begin
|
|
Result := (Left <> InvalidD) and (Top <> InvalidD)
|
|
and (Width >= 0) and (Height >= 0);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function TRectWH.Right: double;
|
|
begin
|
|
Result := Left + Width;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function TRectWH.Bottom: double;
|
|
begin
|
|
Result := Top + Height;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function TRectWH.Contains(const Pt: TPoint): Boolean;
|
|
begin
|
|
Result := (pt.X >= Left) and (pt.X <= Left + Width) and
|
|
(pt.Y >= Top) and (pt.Y <= Top + Height)
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function TRectWH.Contains(const Pt: TPointD): Boolean;
|
|
begin
|
|
Result := (pt.X >= Left) and (pt.X <= Left + Width) and
|
|
(pt.Y >= Top) and (pt.Y <= Top + Height)
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function TRectWH.MidPoint: TPointD;
|
|
begin
|
|
Result := PointD(left + Width * 0.5, top + Height * 0.5);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function TRectWH.RectD: TRectD;
|
|
begin
|
|
Result := Img32.RectD(left, top, left + Width, top + Height);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function TRectWH.Rect: TRect;
|
|
begin
|
|
Result := Img32.Vector.Rect(RectD);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function RectWH(left, top, width, height: integer): TRectWH;
|
|
begin
|
|
Result.Left := left;
|
|
Result.Top := top;
|
|
Result.Width := width;
|
|
Result.Height := height;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function RectWH(left, top, width, height: double): TRectWH;
|
|
begin
|
|
Result.Left := left;
|
|
Result.Top := top;
|
|
Result.Width := width;
|
|
Result.Height := height;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function RectWH(const rec: TRectD): TRectWH;
|
|
begin
|
|
Result.Left := rec.left;
|
|
Result.Top := rec.top;
|
|
Result.Width := rec.width;
|
|
Result.Height := rec.height;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function RectsEqual(const rec1, rec2: TRect): Boolean;
|
|
begin
|
|
result := (rec1.Left = rec2.Left) and (rec1.Top = rec2.Top) and
|
|
(rec1.Right = rec2.Right) and (rec1.Bottom = rec2.Bottom);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Rect(const left, top, right, bottom: integer): TRect;
|
|
begin
|
|
Result.Left := left;
|
|
Result.Top := top;
|
|
Result.Right := right;
|
|
Result.Bottom := bottom;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function IsValid(value: integer): Boolean;
|
|
begin
|
|
Result := value <> -MaxInt;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function IsValid(value: double): Boolean;
|
|
begin
|
|
Result := value <> InvalidD;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function IsValid(const pt: TPoint): Boolean;
|
|
begin
|
|
result := (pt.X <> Invalid) and (pt.Y <> Invalid);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function IsValid(const pt: TPointD): Boolean;
|
|
begin
|
|
result := (pt.X <> -Infinity) and (pt.Y <> -Infinity);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function IsValid(const rec: TRect): Boolean;
|
|
begin
|
|
result := (rec.Left <> MaxInt) and (rec.Top <> MaxInt);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Point(X,Y: Integer): TPoint;
|
|
begin
|
|
result.X := X;
|
|
result.Y := Y;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Point(const pt: TPointD): TPoint;
|
|
begin
|
|
result.X := Round(pt.x);
|
|
result.Y := Round(pt.y);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function PointsEqual(const pt1, pt2: TPointD): Boolean;
|
|
begin
|
|
result := (pt1.X = pt2.X) and (pt1.Y = pt2.Y);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function PointsNearEqual(const pt1, pt2: TPoint; dist: integer): Boolean;
|
|
begin
|
|
Result := (Abs(pt1.X - pt2.X) <= dist) and (Abs(pt1.Y - pt2.Y) < dist);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function PointsNearEqual(const pt1, pt2: TPointD; distSqrd: double): Boolean;
|
|
begin
|
|
Result := Sqr(pt1.X - pt2.X) + Sqr(pt1.Y - pt2.Y) < distSqrd;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function StripNearDuplicates(const path: TPathD;
|
|
minDist: double; isClosedPath: Boolean): TPathD;
|
|
var
|
|
i,j, len: integer;
|
|
begin
|
|
len := length(path);
|
|
SetLength(Result, len);
|
|
if len = 0 then Exit;
|
|
Result[0] := path[0];
|
|
j := 0;
|
|
minDist := minDist * minDist;
|
|
for i := 1 to len -1 do
|
|
if not PointsNearEqual(Result[j], path[i], minDist) then
|
|
begin
|
|
inc(j);
|
|
Result[j] := path[i];
|
|
end;
|
|
if isClosedPath and
|
|
PointsNearEqual(Result[j], Result[0], minDist) then dec(j);
|
|
SetLength(Result, j +1);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function StripNearDuplicates(const paths: TPathsD;
|
|
minLength: double; isClosedPaths: Boolean): TPathsD;
|
|
var
|
|
i, len: integer;
|
|
begin
|
|
len := Length(paths);
|
|
SetLength(Result, len);
|
|
for i := 0 to len -1 do
|
|
Result[i] := StripNearDuplicates(paths[i], minLength, isClosedPaths);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function ValueAlmostZero(val: double; epsilon: double = 0.001): Boolean;
|
|
{$IFDEF INLINE} inline; {$ENDIF}
|
|
begin
|
|
Result := Abs(val) < epsilon;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function ValueAlmostOne(val: double; epsilon: double = 0.001): Boolean;
|
|
{$IFDEF INLINE} inline; {$ENDIF}
|
|
begin
|
|
Result := Abs(val-1) < epsilon;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
procedure GetSinCos(angle: double; out sinA, cosA: double);
|
|
{$IFDEF INLINE} inline; {$ENDIF}
|
|
{$IFNDEF FPC}
|
|
var s, c: extended;
|
|
{$ENDIF}
|
|
begin
|
|
{$IFDEF FPC}
|
|
Math.SinCos(angle, sinA, cosA);
|
|
{$ELSE}
|
|
Math.SinCos(angle, s, c);
|
|
sinA := s; cosA := c;
|
|
{$ENDIF}
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetRotatedRectBounds(const rec: TRect; angle: double): TRect;
|
|
var
|
|
sinA, cosA: double;
|
|
w,h, recW, recH: integer;
|
|
mp: TPoint;
|
|
begin
|
|
NormalizeAngle(angle);
|
|
if angle <> 0 then
|
|
begin
|
|
GetSinCos(angle, sinA, cosA); //the sign of the angle isn't important
|
|
sinA := Abs(sinA); cosA := Abs(cosA);
|
|
RectWidthHeight(rec, recW, recH);
|
|
w := Ceil((recW *cosA + recH *sinA) /2);
|
|
h := Ceil((recW *sinA + recH *cosA) /2);
|
|
mp := MidPoint(rec);
|
|
Result := Rect(mp.X - w, mp.Y - h, mp.X + w, mp.Y +h);
|
|
end
|
|
else
|
|
Result := rec;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetRotatedRectBounds(const rec: TRectD; angle: double): TRectD;
|
|
var
|
|
sinA, cosA: double;
|
|
w,h: double;
|
|
mp: TPointD;
|
|
begin
|
|
NormalizeAngle(angle);
|
|
if angle <> 0 then
|
|
begin
|
|
GetSinCos(angle, sinA, cosA); //the sign of the angle isn't important
|
|
sinA := Abs(sinA); cosA := Abs(cosA);
|
|
w := (rec.Width *cosA + rec.Height *sinA) /2;
|
|
h := (rec.Width *sinA + rec.Height *cosA) /2;
|
|
mp := rec.MidPoint;
|
|
Result := RectD(mp.X - w, mp.Y - h, mp.X + w, mp.Y +h);
|
|
end
|
|
else
|
|
Result := rec;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
function Rect(const recD: TRectD): TRect;
|
|
begin
|
|
Result.Left := Floor(recD.Left);
|
|
Result.Top := Floor(recD.Top);
|
|
Result.Right := Ceil(recD.Right);
|
|
Result.Bottom := Ceil(recD.Bottom);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function PtInRect(const rec: TRectD; const pt: TPointD): Boolean;
|
|
begin
|
|
Result := (pt.X >= rec.Left) and (pt.X < rec.Right) and
|
|
(pt.Y >= rec.Top) and (pt.Y < rec.Bottom);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Size(cx, cy: integer): TSize;
|
|
begin
|
|
Result.cx := cx;
|
|
Result.cy := cy;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function SizeD(cx, cy: double): TSizeD;
|
|
begin
|
|
Result.cx := cx;
|
|
Result.cy := cy;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function IsClockwise(const path: TPathD): Boolean;
|
|
begin
|
|
Result := Area(path) > 0;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Area(const path: TPathD): Double;
|
|
var
|
|
i, j, highI: Integer;
|
|
d: Double;
|
|
begin
|
|
Result := 0.0;
|
|
highI := High(path);
|
|
if (highI < 2) then Exit;
|
|
j := highI;
|
|
for i := 0 to highI do
|
|
begin
|
|
d := (path[j].X + path[i].X);
|
|
Result := Result + d * (path[j].Y - path[i].Y);
|
|
j := i;
|
|
end;
|
|
Result := -Result * 0.5;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
procedure OffsetRect(var rec: TRectD; dx, dy: double);
|
|
begin
|
|
rec.Left := rec.Left + dx;
|
|
rec.Top := rec.Top + dy;
|
|
rec.Right := rec.Right + dx;
|
|
rec.Bottom := rec.Bottom + dy;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function MakeSquare(rec: TRect): TRect;
|
|
var
|
|
i: integer;
|
|
begin
|
|
Result := rec;
|
|
i := ((rec.Right - rec.Left) + (rec.Bottom - rec.Top)) div 2;
|
|
Result.Right := Result.Left + i;
|
|
Result.Bottom := Result.Top + i;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function MidPoint(const rec: TRect): TPoint;
|
|
begin
|
|
Result.X := (rec.Left + rec.Right) div 2;
|
|
Result.Y := (rec.Top + rec.Bottom) div 2;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function MidPoint(const rec: TRectD): TPointD;
|
|
begin
|
|
Result.X := (rec.Left + rec.Right) * 0.5;
|
|
Result.Y := (rec.Top + rec.Bottom) * 0.5;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function MidPoint(const pt1, pt2: TPoint): TPoint;
|
|
begin
|
|
Result.X := (pt1.X + pt2.X) div 2;
|
|
Result.Y := (pt1.Y + pt2.Y) div 2;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function MidPoint(const pt1, pt2: TPointD): TPointD;
|
|
begin
|
|
Result.X := (pt1.X + pt2.X) * 0.5;
|
|
Result.Y := (pt1.Y + pt2.Y) * 0.5;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Average(val1, val2: integer): integer;
|
|
begin
|
|
Result := (val1 + val2) div 2;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Average(val1, val2: double): double;
|
|
begin
|
|
Result := (val1 + val2) * 0.5;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function RectsOverlap(const rec1, rec2: TRect): Boolean;
|
|
begin
|
|
Result := (rec1.Left < rec2.Right) and (rec1.Right > rec2.Left) and
|
|
(rec1.Top < rec2.Bottom) and (rec1.Bottom > rec2.Top);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function IsSameRect(const rec1, rec2: TRect): Boolean;
|
|
begin
|
|
Result := (rec1.Left = rec2.Left) and (rec1.Top = rec2.Top) and
|
|
(rec1.Right = rec2.Right) and (rec1.Bottom = rec2.Bottom);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function RectsIntersect(const rec1, rec2: TRect): Boolean;
|
|
var
|
|
dummy: TRect;
|
|
begin
|
|
Result := Types.IntersectRect(dummy, rec1, rec2);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function RectsIntersect(const rec1, rec2: TRectD): Boolean;
|
|
begin
|
|
Result := not IntersectRect(rec1, rec2).IsEmpty;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function IntersectRect(const rec1, rec2: TRectD): TRectD;
|
|
begin
|
|
result.Left := Max(rec1.Left, rec2.Left);
|
|
result.Top := Max(rec1.Top, rec2.Top);
|
|
result.Right := Min(rec1.Right, rec2.Right);
|
|
result.Bottom := Min(rec1.Bottom, rec2.Bottom);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function UnionRect(const rec1, rec2: TRect): TRect;
|
|
begin
|
|
if IsEmptyRect(rec1) then
|
|
Result := rec2
|
|
else if IsEmptyRect(rec2) then
|
|
Result := rec1
|
|
else
|
|
begin
|
|
result.Left := Min(rec1.Left, rec2.Left);
|
|
result.Top := Min(rec1.Top, rec2.Top);
|
|
result.Right := Max(rec1.Right, rec2.Right);
|
|
result.Bottom := Max(rec1.Bottom, rec2.Bottom);
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function UnionRect(const rec1, rec2: TRectD): TRectD;
|
|
begin
|
|
if IsEmptyRect(rec1) then
|
|
Result := rec2
|
|
else if IsEmptyRect(rec2) then
|
|
Result := rec1
|
|
else
|
|
begin
|
|
result.Left := Min(rec1.Left, rec2.Left);
|
|
result.Top := Min(rec1.Top, rec2.Top);
|
|
result.Right := Max(rec1.Right, rec2.Right);
|
|
result.Bottom := Max(rec1.Bottom, rec2.Bottom);
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function MakeArrayOfInteger(const ints: array of integer): TArrayOfInteger;
|
|
var
|
|
i, len: integer;
|
|
begin
|
|
len := Length(ints);
|
|
SetLength(Result, len);
|
|
for i := 0 to len -1 do Result[i] := ints[i];
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function MakeArrayOfDouble(const doubles: array of double): TArrayOfDouble;
|
|
var
|
|
i, len: integer;
|
|
begin
|
|
len := Length(doubles);
|
|
SetLength(Result, len);
|
|
for i := 0 to len -1 do Result[i] := doubles[i];
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function CrossProduct(const vector1, vector2: TPointD): double;
|
|
begin
|
|
result := vector1.X * vector2.Y - vector2.X * vector1.Y;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function CrossProduct(const pt1, pt2, pt3: TPointD): double;
|
|
var
|
|
x1,x2,y1,y2: double;
|
|
begin
|
|
x1 := pt2.X - pt1.X;
|
|
y1 := pt2.Y - pt1.Y;
|
|
x2 := pt3.X - pt2.X;
|
|
y2 := pt3.Y - pt2.Y;
|
|
result := (x1 * y2 - y1 * x2);
|
|
end;
|
|
//---------------------------------------------------------------------------
|
|
|
|
function CrossProduct(const pt1, pt2, pt3, pt4: TPointD): double;
|
|
var
|
|
x1,x2,y1,y2: double;
|
|
begin
|
|
x1 := pt2.X - pt1.X;
|
|
y1 := pt2.Y - pt1.Y;
|
|
x2 := pt4.X - pt3.X;
|
|
y2 := pt4.Y - pt3.Y;
|
|
result := (x1 * y2 - y1 * x2);
|
|
end;
|
|
//---------------------------------------------------------------------------
|
|
|
|
function DotProduct(const vector1, vector2: TPointD): double;
|
|
begin
|
|
result := vector1.X * vector2.X + vector1.Y * vector2.Y;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function DotProduct(const pt1, pt2, pt3: TPointD): double;
|
|
var
|
|
x1,x2,y1,y2: double;
|
|
begin
|
|
x1 := pt2.X - pt1.X;
|
|
y1 := pt2.Y - pt1.Y;
|
|
x2 := pt2.X - pt3.X;
|
|
y2 := pt2.Y - pt3.Y;
|
|
result := (x1 * x2 + y1 * y2);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function TurnsLeft(const pt1, pt2, pt3: TPointD): boolean;
|
|
begin
|
|
result := CrossProduct(pt1, pt2, pt3) < 0;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function TurnsRight(const pt1, pt2, pt3: TPointD): boolean;
|
|
begin
|
|
result := CrossProduct(pt1, pt2, pt3) > 0;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function IsPathConvex(const path: TPathD): Boolean;
|
|
var
|
|
i, pathLen: integer;
|
|
dir: boolean;
|
|
begin
|
|
result := false;
|
|
pathLen := length(path);
|
|
if pathLen < 3 then Exit;
|
|
//get the winding direction of the first angle
|
|
dir := TurnsRight(path[0], path[1], path[2]);
|
|
//check that each other angle has the same winding direction
|
|
for i := 1 to pathLen -1 do
|
|
if TurnsRight(path[i], path[(i+1) mod pathLen],
|
|
path[(i+2) mod pathLen]) <> dir then Exit;
|
|
result := true;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetUnitVector(const pt1, pt2: TPointD): TPointD;
|
|
var
|
|
dx, dy, inverseHypot: Double;
|
|
begin
|
|
if (pt1.x = pt2.x) and (pt1.y = pt2.y) then
|
|
begin
|
|
Result.X := 0;
|
|
Result.Y := 0;
|
|
Exit;
|
|
end;
|
|
dx := (pt2.X - pt1.X);
|
|
dy := (pt2.Y - pt1.Y);
|
|
inverseHypot := 1 / Hypot(dx, dy);
|
|
dx := dx * inverseHypot;
|
|
dy := dy * inverseHypot;
|
|
Result.X := dx;
|
|
Result.Y := dy;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetUnitNormal(const pt1, pt2: TPointD): TPointD;
|
|
begin
|
|
if not GetUnitNormal(pt1, pt2, Result) then
|
|
Result := NullPointD;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetUnitNormal(const pt1, pt2: TPointD; out norm: TPointD): Boolean;
|
|
var
|
|
dx, dy, inverseHypot: Double;
|
|
begin
|
|
result := not PointsNearEqual(pt1, pt2, 0.001);
|
|
if not result then Exit;
|
|
dx := (pt2.X - pt1.X);
|
|
dy := (pt2.Y - pt1.Y);
|
|
inverseHypot := 1 / Hypot(dx, dy);
|
|
dx := dx * inverseHypot;
|
|
dy := dy * inverseHypot;
|
|
norm.X := dy;
|
|
norm.Y := -dx
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function NormalizeVector(const vec: TPointD): TPointD;
|
|
var
|
|
h, inverseHypot: Double;
|
|
begin
|
|
h := Hypot(vec.X, vec.Y);
|
|
if ValueAlmostZero(h, 0.001) then
|
|
begin
|
|
Result := NullPointD;
|
|
Exit;
|
|
end;
|
|
inverseHypot := 1 / h;
|
|
Result.X := vec.X * inverseHypot;
|
|
Result.Y := vec.Y * inverseHypot;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetAvgUnitVector(const vec1, vec2: TPointD): TPointD;
|
|
begin
|
|
Result := NormalizeVector(PointD(vec1.X + vec2.X, vec1.Y + vec2.Y));
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Paths(const path: TPathD): TPathsD;
|
|
begin
|
|
SetLength(Result, 1);
|
|
result[0] := Copy(path, 0, length(path));
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function CopyPath(const path: TPathD): TPathD;
|
|
begin
|
|
Result := Copy(path, 0, Length(path));
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function CopyPaths(const paths: TPathsD): TPathsD;
|
|
var
|
|
i, len1: integer;
|
|
begin
|
|
len1 := length(paths);
|
|
setLength(result, len1);
|
|
for i := 0 to len1 -1 do
|
|
result[i] := Copy(paths[i], 0, length(paths[i]));
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function OffsetPoint(const pt: TPoint; dx, dy: integer): TPoint;
|
|
begin
|
|
result.x := pt.x + dx;
|
|
result.y := pt.y + dy;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function OffsetPoint(const pt: TPointD; dx, dy: double): TPointD;
|
|
begin
|
|
result.x := pt.x + dx;
|
|
result.y := pt.y + dy;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function OffsetPath(const path: TPathD; dx, dy: double): TPathD;
|
|
var
|
|
i, len: integer;
|
|
begin
|
|
len := length(path);
|
|
setLength(result, len);
|
|
for i := 0 to len -1 do
|
|
begin
|
|
result[i].x := path[i].x + dx;
|
|
result[i].y := path[i].y + dy;
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function OffsetPath(const paths: TPathsD;
|
|
dx, dy: double): TPathsD;
|
|
var
|
|
i,len: integer;
|
|
begin
|
|
len := length(paths);
|
|
setLength(result, len);
|
|
for i := 0 to len -1 do
|
|
result[i] := OffsetPath(paths[i], dx, dy);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function OffsetPath(const ppp: TArrayOfPathsD; dx, dy: double): TArrayOfPathsD;
|
|
var
|
|
i,len: integer;
|
|
begin
|
|
len := length(ppp);
|
|
setLength(result, len);
|
|
for i := 0 to len -1 do
|
|
result[i] := OffsetPath(ppp[i], dx, dy);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function ScalePoint(const pt: TPointD; scale: double): TPointD;
|
|
begin
|
|
Result.X := pt.X * scale;
|
|
Result.Y := pt.Y * scale;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function ScalePoint(const pt: TPointD; sx, sy: double): TPointD;
|
|
begin
|
|
Result.X := pt.X * sx;
|
|
Result.Y := pt.Y * sy;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function ScalePath(const path: TPathD; sx, sy: double): TPathD;
|
|
var
|
|
i, len: integer;
|
|
begin
|
|
if (sx = 0) or (sy = 0) then
|
|
Result := nil
|
|
else if ((sx = 1) and (sy = 1)) then
|
|
begin
|
|
Result := Copy(path, 0, Length(path));
|
|
end else
|
|
begin
|
|
len := length(path);
|
|
setLength(result, len);
|
|
for i := 0 to len -1 do
|
|
begin
|
|
result[i].x := path[i].x * sx;
|
|
result[i].y := path[i].y * sy;
|
|
end;
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function ScalePath(const path: TPathD;
|
|
scale: double): TPathD;
|
|
begin
|
|
result := ScalePath(path, scale, scale);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function ScalePath(const paths: TPathsD;
|
|
sx, sy: double): TPathsD;
|
|
var
|
|
i,len: integer;
|
|
begin
|
|
len := length(paths);
|
|
setLength(result, len);
|
|
for i := 0 to len -1 do
|
|
result[i] := ScalePath(paths[i], sx, sy);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function ScalePath(const paths: TPathsD;
|
|
scale: double): TPathsD;
|
|
begin
|
|
result := ScalePath(paths, scale, scale);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function ScaleRect(const rec: TRect; scale: double): TRect;
|
|
begin
|
|
result := rec;
|
|
Result.Left := Round(Result.Left * scale);
|
|
Result.Top := Round(Result.Top * scale);
|
|
Result.Right := Round(Result.Right * scale);
|
|
Result.Bottom := Round(Result.Bottom * scale);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function ScaleRect(const rec: TRect; sx, sy: double): TRect;
|
|
begin
|
|
result := rec;
|
|
Result.Left := Round(Result.Left * sx);
|
|
Result.Top := Round(Result.Top * sy);
|
|
Result.Right := Round(Result.Right * sx);
|
|
Result.Bottom := Round(Result.Bottom * sy);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function ScaleRect(const rec: TRectD; scale: double): TRectD;
|
|
begin
|
|
result := rec;
|
|
Result.Left := Result.Left * scale;
|
|
Result.Top := Result.Top * scale;
|
|
Result.Right := Result.Right * scale;
|
|
Result.Bottom := Result.Bottom * scale;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function ScaleRect(const rec: TRectD; sx, sy: double): TRectD;
|
|
begin
|
|
result := rec;
|
|
Result.Left := Result.Left * sx;
|
|
Result.Top := Result.Top * sy;
|
|
Result.Right := Result.Right * sx;
|
|
Result.Bottom := Result.Bottom * sy;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function ReversePath(const path: TPathD): TPathD;
|
|
var
|
|
i, highI: integer;
|
|
begin
|
|
highI := High(path);
|
|
SetLength(result, highI +1);
|
|
for i := 0 to highI do
|
|
result[i] := path[highI -i];
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function ReversePath(const paths: TPathsD): TPathsD;
|
|
var
|
|
i, len: integer;
|
|
begin
|
|
len := Length(paths);
|
|
SetLength(result, len);
|
|
for i := 0 to len -1 do
|
|
result[i] := ReversePath(paths[i]);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function OpenPathToFlatPolygon(const path: TPathD): TPathD;
|
|
var
|
|
i, len, len2: integer;
|
|
begin
|
|
len := Length(path);
|
|
len2 := Max(0, len - 2);
|
|
setLength(Result, len + len2);
|
|
if len = 0 then Exit;
|
|
Move(path[0], Result[0], len * SizeOf(TPointD));
|
|
if len2 = 0 then Exit;
|
|
for i := 0 to len - 3 do
|
|
result[len + i] := path[len - 2 -i];
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetVectors(const path: TPathD): TPathD;
|
|
var
|
|
i,j, len: cardinal;
|
|
pt: TPointD;
|
|
begin
|
|
len := length(path);
|
|
setLength(result, len);
|
|
if len = 0 then Exit;
|
|
pt := path[0];
|
|
//skip duplicates
|
|
i := len -1;
|
|
while (i > 0) and
|
|
(path[i].X = pt.X) and (path[i].Y = pt.Y) do dec(i);
|
|
if (i = 0) then
|
|
begin
|
|
//all points are equal!
|
|
for i := 0 to len -1 do result[i] := PointD(0,0);
|
|
Exit;
|
|
end;
|
|
result[i] := GetUnitVector(path[i], pt);
|
|
//fix up any duplicates at the end of the path
|
|
for j := i +1 to len -1 do
|
|
result[j] := result[j-1];
|
|
//with at least one valid vector, we can now
|
|
//safely get the remaining vectors
|
|
pt := path[i];
|
|
for i := i -1 downto 0 do
|
|
begin
|
|
if (path[i].X <> pt.X) or (path[i].Y <> pt.Y) then
|
|
begin
|
|
result[i] := GetUnitVector(path[i], pt);
|
|
pt := path[i];
|
|
end else
|
|
result[i] := result[i+1]
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetNormals(const path: TPathD): TPathD;
|
|
var
|
|
i, highI: integer;
|
|
last: TPointD;
|
|
begin
|
|
highI := High(path);
|
|
setLength(result, highI+1);
|
|
if highI < 0 then Exit;
|
|
|
|
last := NullPointD;
|
|
for i := 0 to highI -1 do
|
|
begin
|
|
if GetUnitNormal(path[i], path[i+1], result[i]) then
|
|
last := result[i] else
|
|
result[i] := last;
|
|
end;
|
|
if GetUnitNormal(path[highI], path[0], result[highI]) then
|
|
last := result[highI];
|
|
|
|
for i := 0 to highI do
|
|
begin
|
|
if (result[i].X <> 0) or (result[i].Y <> 0) then Break;
|
|
result[i] := last;
|
|
end;
|
|
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function DistanceSqrd(const pt1, pt2: TPoint): double;
|
|
begin
|
|
result := Sqr(pt1.X - pt2.X) + Sqr(pt1.Y - pt2.Y);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function DistanceSqrd(const pt1, pt2: TPointD): double;
|
|
begin
|
|
result := Sqr(pt1.X - pt2.X) + Sqr(pt1.Y - pt2.Y);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Distance(const pt1, pt2: TPoint): double;
|
|
begin
|
|
Result := Sqrt(DistanceSqrd(pt1, pt2));
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Distance(const pt1, pt2: TPointD): double;
|
|
begin
|
|
Result := Sqrt(DistanceSqrd(pt1, pt2));
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Distance(const path: TPathD; stopAt: integer): double;
|
|
var
|
|
i, highI: integer;
|
|
begin
|
|
Result := 0;
|
|
highI := High(path);
|
|
if (stopAt > 0) and (stopAt < HighI) then highI := stopAt;
|
|
for i := 1 to highI do
|
|
Result := Result + Distance(path[i-1],path[i]);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetDistances(const path: TPathD): TArrayOfDouble;
|
|
var
|
|
i, len: integer;
|
|
begin
|
|
len := Length(path);
|
|
SetLength(Result, len);
|
|
if len = 0 then Exit;
|
|
Result[0] := 0;
|
|
for i := 1 to len -1 do
|
|
Result[i] := Distance(path[i-1], path[i]);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetCumulativeDistances(const path: TPathD): TArrayOfDouble;
|
|
var
|
|
i, len: integer;
|
|
begin
|
|
len := Length(path);
|
|
SetLength(Result, len);
|
|
if len = 0 then Exit;
|
|
Result[0] := 0;
|
|
for i := 1 to len -1 do
|
|
Result[i] := Result[i-1] + Distance(path[i-1], path[i]);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function PerpendicularDistSqrd(const pt, line1, line2: TPointD): double;
|
|
var
|
|
a,b,c,d: double;
|
|
begin
|
|
if PointsEqual(line1, line2) then
|
|
begin
|
|
Result := DistanceSqrd(pt, line1);
|
|
end else
|
|
begin
|
|
a := pt.X - line1.X;
|
|
b := pt.Y - line1.Y;
|
|
c := line2.X - line1.X;
|
|
d := line2.Y - line1.Y;
|
|
if (c = 0) and (d = 0) then
|
|
result := 0 else
|
|
result := Sqr(a * d - c * b) / (c * c + d * d);
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function PointInPolyWindingCount(const pt: TPointD;
|
|
const path: TPathD; out PointOnEdgeDir: integer): integer;
|
|
var
|
|
i, len: integer;
|
|
prevPt: TPointD;
|
|
isAbove: Boolean;
|
|
crossProd: double;
|
|
begin
|
|
//nb: PointOnEdgeDir == 0 unless 'pt' is on 'path'
|
|
Result := 0;
|
|
PointOnEdgeDir := 0;
|
|
i := 0;
|
|
len := Length(path);
|
|
if len = 0 then Exit;
|
|
prevPt := path[len-1];
|
|
while (i < len) and (path[i].Y = prevPt.Y) do inc(i);
|
|
if i = len then Exit;
|
|
isAbove := (prevPt.Y < pt.Y);
|
|
while (i < len) do
|
|
begin
|
|
if isAbove then
|
|
begin
|
|
while (i < len) and (path[i].Y < pt.Y) do inc(i);
|
|
if i = len then break
|
|
else if i > 0 then prevPt := path[i -1];
|
|
crossProd := CrossProduct(prevPt, path[i], pt);
|
|
if crossProd = 0 then
|
|
begin
|
|
PointOnEdgeDir := -1;
|
|
//nb: could safely exit here with frNonZero or frEvenOdd fill rules
|
|
end
|
|
else if crossProd < 0 then dec(Result);
|
|
end else
|
|
begin
|
|
while (i < len) and (path[i].Y > pt.Y) do inc(i);
|
|
if i = len then break
|
|
else if i > 0 then prevPt := path[i -1];
|
|
crossProd := CrossProduct(prevPt, path[i], pt);
|
|
if crossProd = 0 then
|
|
begin
|
|
PointOnEdgeDir := 1;
|
|
//nb: could safely exit here with frNonZero or frEvenOdd fill rules
|
|
end
|
|
else if crossProd > 0 then inc(Result);
|
|
end;
|
|
inc(i);
|
|
isAbove := not isAbove;
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function PointInPolygon(const pt: TPointD;
|
|
const polygon: TPathD; fillRule: TFillRule): Boolean;
|
|
var
|
|
wc: integer;
|
|
PointOnEdgeDir: integer;
|
|
begin
|
|
wc := PointInPolyWindingCount(pt, polygon, PointOnEdgeDir);
|
|
case fillRule of
|
|
frEvenOdd: result := (PointOnEdgeDir <> 0) or Odd(wc);
|
|
frNonZero: result := (PointOnEdgeDir <> 0) or (wc <> 0);
|
|
frPositive: result := (PointOnEdgeDir + wc > 0);
|
|
else {frNegative} result := (PointOnEdgeDir + wc < 0);
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function PointInPolysWindingCount(const pt: TPointD;
|
|
const paths: TPathsD; out PointOnEdgeDir: integer): integer;
|
|
var
|
|
i,j, len: integer;
|
|
p: TPathD;
|
|
prevPt: TPointD;
|
|
isAbove: Boolean;
|
|
crossProd: double;
|
|
begin
|
|
//nb: PointOnEdgeDir == 0 unless 'pt' is on 'path'
|
|
Result := 0;
|
|
PointOnEdgeDir := 0;
|
|
for i := 0 to High(paths) do
|
|
begin
|
|
j := 0;
|
|
p := paths[i];
|
|
len := Length(p);
|
|
if len < 3 then Continue;
|
|
prevPt := p[len-1];
|
|
while (j < len) and (p[j].Y = prevPt.Y) do inc(j);
|
|
if j = len then continue;
|
|
isAbove := (prevPt.Y < pt.Y);
|
|
while (j < len) do
|
|
begin
|
|
if isAbove then
|
|
begin
|
|
while (j < len) and (p[j].Y < pt.Y) do inc(j);
|
|
if j = len then break
|
|
else if j > 0 then prevPt := p[j -1];
|
|
crossProd := CrossProduct(prevPt, p[j], pt);
|
|
if crossProd = 0 then PointOnEdgeDir := -1
|
|
else if crossProd < 0 then dec(Result);
|
|
end else
|
|
begin
|
|
while (j < len) and (p[j].Y > pt.Y) do inc(j);
|
|
if j = len then break
|
|
else if j > 0 then prevPt := p[j -1];
|
|
crossProd := CrossProduct(prevPt, p[j], pt);
|
|
if crossProd = 0 then PointOnEdgeDir := 1
|
|
else if crossProd > 0 then inc(Result);
|
|
end;
|
|
inc(j);
|
|
isAbove := not isAbove;
|
|
end;
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function PointInPolygons(const pt: TPointD;
|
|
const polygons: TPathsD; fillRule: TFillRule): Boolean;
|
|
var
|
|
wc: integer;
|
|
PointOnEdgeDir: integer;
|
|
begin
|
|
wc := PointInPolysWindingCount(pt, polygons, PointOnEdgeDir);
|
|
case fillRule of
|
|
frEvenOdd: result := (PointOnEdgeDir <> 0) or Odd(wc);
|
|
frNonZero: result := (PointOnEdgeDir <> 0) or (wc <> 0);
|
|
frPositive: result := (PointOnEdgeDir + wc > 0);
|
|
else {frNegative} result := (PointOnEdgeDir + wc < 0);
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function PerpendicularDist(const pt, line1, line2: TPointD): double;
|
|
var
|
|
a,b,c,d: double;
|
|
begin
|
|
//given: cross product of 2 vectors = area of parallelogram
|
|
//and given: area of parallelogram = length base * height
|
|
//height (ie perpendic. dist.) = cross product of 2 vectors / length base
|
|
a := pt.X - line1.X;
|
|
b := pt.Y - line1.Y;
|
|
c := line2.X - line1.X;
|
|
d := line2.Y - line1.Y;
|
|
result := abs(a * d - c * b) / Sqrt(c * c + d * d);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function ClosestPoint(const pt, linePt1, linePt2: TPointD;
|
|
constrainToSegment: Boolean): TPointD;
|
|
var
|
|
q: double;
|
|
begin
|
|
if (linePt1.X = linePt2.X) and (linePt1.Y = linePt2.Y) then
|
|
begin
|
|
Result := linePt1;
|
|
end else
|
|
begin
|
|
q := ((pt.X-linePt1.X)*(linePt2.X-linePt1.X) +
|
|
(pt.Y-linePt1.Y)*(linePt2.Y-linePt1.Y)) /
|
|
(sqr(linePt2.X-linePt1.X) + sqr(linePt2.Y-linePt1.Y));
|
|
if constrainToSegment then
|
|
begin
|
|
if q < 0 then q := 0 else if q > 1 then q := 1;
|
|
end;
|
|
Result.X := (1-q)*linePt1.X + q*linePt2.X;
|
|
Result.Y := (1-q)*linePt1.Y + q*linePt2.Y;
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function ClosestPointOnLine(const pt, linePt1, linePt2: TPointD): TPointD;
|
|
begin
|
|
result := ClosestPoint(pt, linePt1, linePt2, false);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function ClosestPointOnSegment(const pt, segPt1, segPt2: TPointD): TPointD;
|
|
begin
|
|
result := ClosestPoint(pt, segPt1, segPt2, true);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetPtOnEllipseFromAngle(const ellipseRect: TRectD;
|
|
angle: double): TPointD;
|
|
var
|
|
sn, co: double;
|
|
begin
|
|
NormalizeAngle(angle);
|
|
GetSinCos(angle, sn, co);
|
|
Result.X := ellipseRect.MidPoint.X + ellipseRect.Width/2 * co;
|
|
Result.Y := ellipseRect.MidPoint.Y + ellipseRect.Height/2 * sn;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetEllipticalAngleFromPoint(const ellipseRect: TRectD;
|
|
const pt: TPointD): double;
|
|
begin
|
|
with ellipseRect do
|
|
Result := ArcTan2(Width/Height * (pt.Y - MidPoint.Y), (pt.X - MidPoint.X));
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetRotatedEllipticalAngleFromPoint(const ellipseRect: TRectD;
|
|
ellipseRotAngle: double; pt: TPointD): double;
|
|
begin
|
|
Result := 0;
|
|
if ellipseRect.IsEmpty then Exit;
|
|
RotatePoint(pt, ellipseRect.MidPoint, -ellipseRotAngle);
|
|
Result := GetEllipticalAngleFromPoint(ellipseRect, pt);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetPtOnRotatedEllipseFromAngle(const ellipseRect: TRectD;
|
|
ellipseRotAngle, angle: double): TPointD;
|
|
begin
|
|
Result := GetPtOnEllipseFromAngle(ellipseRect, angle);
|
|
if ellipseRotAngle <> 0 then
|
|
img32.Vector.RotatePoint(Result, ellipseRect.MidPoint, ellipseRotAngle);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetClosestPtOnRotatedEllipse(const ellipseRect: TRectD;
|
|
ellipseRotation: double; const pt: TPointD): TPointD;
|
|
var
|
|
pt2: TPointD;
|
|
angle: double;
|
|
begin
|
|
pt2 := pt;
|
|
Img32.Vector.RotatePoint(pt2, ellipseRect.MidPoint, -ellipseRotation);
|
|
angle := GetEllipticalAngleFromPoint(ellipseRect, pt2);
|
|
Result := GetPtOnEllipseFromAngle(ellipseRect, angle);
|
|
Img32.Vector.RotatePoint(Result, ellipseRect.MidPoint, ellipseRotation);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function IsPointInEllipse(const ellipseRec: TRect; const pt: TPoint): Boolean;
|
|
var
|
|
rec: TRectD;
|
|
w,h: integer;
|
|
x,y, y2, a,b, dx,dy: double;
|
|
begin
|
|
RectWidthHeight(ellipseRec, w, h);
|
|
a := w * 0.5;
|
|
b := h * 0.5;
|
|
dx := ellipseRec.Left + a;
|
|
dy := ellipseRec.Top + b;
|
|
rec := RectD(ellipseRec);
|
|
OffsetRect(rec, -dx, -dy);
|
|
x := pt.X -dx; y := pt.Y -dy;
|
|
//first make sure pt is inside rect
|
|
Result := (abs(x) <= a) and (abs(y) <= b);
|
|
if not result then Exit;
|
|
//given (x*x)/(a*a) + (y*y)/(b*b) = 1
|
|
//then y*y = b*b(1 - (x*x)/(a*a))
|
|
//nb: contents of Sqrt below will always be positive
|
|
//since the substituted x must be within ellipseRec bounds
|
|
y2 := Sqrt((b*b*(1 - (x*x)/(a*a))));
|
|
Result := (y >= -y2) and (y <= y2);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetLineEllipseIntersects(const ellipseRec: TRect;
|
|
var linePt1, linePt2: TPointD): Boolean;
|
|
var
|
|
dx, dy, m,a,b,c,q: double;
|
|
qa,qb,qc,qs: double;
|
|
rec: TRectD;
|
|
pt1, pt2: TPointD;
|
|
begin
|
|
rec := RectD(ellipseRec);
|
|
a := rec.Width *0.5;
|
|
b := rec.Height *0.5;
|
|
//offset ellipseRect so it's centered over the coordinate origin
|
|
dx := ellipseRec.Left + a; dy := ellipseRec.Top + b;
|
|
offsetRect(rec, -dx, -dy);
|
|
pt1 := OffsetPoint(linePt1, -dx, -dy);
|
|
pt2 := OffsetPoint(linePt2, -dx, -dy);
|
|
//equation of ellipse = (x*x)/(a*a) + (y*y)/(b*b) = 1
|
|
//equation of line = y = mx + c;
|
|
if (pt1.X = pt2.X) then //vertical line (ie infinite slope)
|
|
begin
|
|
//given x = K, then y*y = b*b(1 - (x*x)/(a*a))
|
|
q := (b*b)*(1 - Sqr(pt1.X)/(a*a));
|
|
result := q >= 0;
|
|
if not result then Exit;
|
|
q := Sqrt(q);
|
|
pt1.Y := q;
|
|
pt2.Y := -q;
|
|
end else
|
|
begin
|
|
//using simultaneous equations and substitution
|
|
//given y = mx + c
|
|
m := (pt1.Y - pt2.Y)/(pt1.X - pt2.X);
|
|
c := pt1.Y - m * pt1.X;
|
|
//given (x*x)/(a*a) + (y*y)/(b*b) = 1
|
|
//(x*x)/(a*a)*(b*b) + (y*y) = (b*b)
|
|
//(b*b)/(a*a) *(x*x) + Sqr(m*x +c) = (b*b)
|
|
//(b*b)/(a*a) *(x*x) + (m*m)*(x*x) + 2*m*x*c +c*c = b*b
|
|
//((b*b)/(a*a) +(m*m)) *(x*x) + 2*m*c*(x) + (c*c) - (b*b) = 0
|
|
//solving quadratic equation
|
|
qa := ((b*b)/(a*a) +(m*m));
|
|
qb := 2*m*c;
|
|
qc := (c*c) - (b*b);
|
|
qs := (qb*qb) - 4*qa*qc;
|
|
Result := qs >= 0;
|
|
if not result then Exit;
|
|
qs := Sqrt(qs);
|
|
pt1.X := (-qb +qs)/(2 * qa);
|
|
pt1.Y := m * pt1.X + c;
|
|
pt2.X := (-qb -qs)/(2 * qa);
|
|
pt2.Y := m * pt2.X + c;
|
|
end;
|
|
//finally reverse initial offset
|
|
linePt1 := OffsetPoint(pt1, dx, dy);
|
|
linePt2 := OffsetPoint(pt2, dx, dy);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Sign(const value: Double): integer; {$IFDEF INLINE} inline; {$ENDIF}
|
|
begin
|
|
if value < 0 then Result := -1
|
|
else if value > 0 then Result := 1
|
|
else Result := 0;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function ApplyNormal(const pt, norm: TPointD; delta: double): TPointD;
|
|
{$IFDEF INLINE} inline; {$ENDIF}
|
|
begin
|
|
result := PointD(pt.X + norm.X * delta, pt.Y + norm.Y * delta);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetParallelOffests(const path, norms: TPathD;
|
|
delta: double): TPathD;
|
|
var
|
|
i, highI, len: integer;
|
|
begin
|
|
len := Length(path);
|
|
highI := len -1;
|
|
SetLength(Result, len *2);
|
|
Result[0] := ApplyNormal(path[0], norms[0], delta);
|
|
for i := 1 to highI do
|
|
begin
|
|
Result[i*2-1] := ApplyNormal(path[i], norms[i-1], delta);
|
|
Result[i*2] := ApplyNormal(path[i], norms[i], delta);
|
|
end;
|
|
Result[highI*2+1] := ApplyNormal(path[0], norms[highI], delta);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
type
|
|
TGrowRec = record
|
|
StepsPerRad : double;
|
|
StepSin : double;
|
|
StepCos : double;
|
|
Radius : double;
|
|
aSin : double;
|
|
aCos : double;
|
|
end;
|
|
|
|
function DoRound(const pt, norm1: TPointD;
|
|
const growRec: TGrowRec): TPathD;
|
|
var
|
|
i, steps: Integer;
|
|
a: Double;
|
|
pt2: TPointD;
|
|
begin
|
|
a := ArcTan2(growRec.aSin, growRec.aCos);
|
|
steps := Round(growRec.StepsPerRad * Abs(a));
|
|
SetLength(Result, steps +1);
|
|
|
|
pt2 := PointD(norm1.x * growRec.Radius, norm1.y * growRec.Radius);
|
|
Result[0] := PointD(pt.x + pt2.x, pt.y + pt2.y);
|
|
with growRec do
|
|
for i := 1 to steps do
|
|
begin
|
|
pt2 := PointD(pt2.X * StepCos - StepSin * pt2.Y,
|
|
pt2.X * StepSin + pt2.Y * StepCos);
|
|
Result[i] := PointD(pt.X + pt2.X, pt.Y + pt2.Y);
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function CalcRoundingSteps(radius: double): double;
|
|
begin
|
|
//the results of this function have been derived empirically
|
|
//and may need further adjustment
|
|
if radius < 0.55 then result := 4
|
|
else result := Pi * Sqrt(radius);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Grow(const path, normals: TPathD; delta: double;
|
|
joinStyle: TJoinStyle; miterLim: double; isOpen: Boolean): TPathD;
|
|
var
|
|
resCnt, resCap: integer;
|
|
norms : TPathD;
|
|
parallelOffsets : TPathD;
|
|
|
|
procedure AddPoint(const pt: TPointD);
|
|
begin
|
|
if resCnt >= resCap then
|
|
begin
|
|
inc(resCap, 64);
|
|
setLength(result, resCap);
|
|
end;
|
|
result[resCnt] := pt;
|
|
inc(resCnt);
|
|
end;
|
|
|
|
procedure DoMiter(i, prevI: integer; cosA: double);
|
|
var
|
|
a: double;
|
|
begin
|
|
a := delta / (1 + cosA); //see offset_triginometry4.svg
|
|
AddPoint(PointD(path[i].X + (norms[i].X + norms[prevI].X) * a,
|
|
path[i].Y + (norms[i].Y + norms[prevI].Y) * a));
|
|
end;
|
|
|
|
procedure DoSquare(i, prevI: integer);
|
|
var
|
|
pt1, pt2, pt3, pt4: TPointD;
|
|
pt, ptQ : TPointD;
|
|
vec : TPointD;
|
|
begin
|
|
// using the reciprocal of unit normals (as unit vectors)
|
|
// get the average unit vector ...
|
|
vec := GetAvgUnitVector(
|
|
PointD(-norms[prevI].Y, norms[prevI].X),
|
|
PointD(norms[i].Y, -norms[i].X));
|
|
// now offset the original vertex delta units along unit vector
|
|
ptQ := OffsetPoint(path[i], delta * vec.X, delta * vec.Y);
|
|
|
|
// get perpendicular vertices
|
|
pt1 := OffsetPoint(ptQ, delta * vec.Y, delta * -vec.X);
|
|
pt2 := OffsetPoint(ptQ, delta * -vec.Y, delta * vec.X);
|
|
// get 2 vertices along one edge offset
|
|
pt3 := parallelOffsets[prevI*2];
|
|
pt4 := parallelOffsets[prevI*2 +1];
|
|
IntersectPoint(pt1,pt2,pt3,pt4, pt);
|
|
AddPoint(pt);
|
|
//get the second intersect point through reflecion
|
|
pt := ReflectPoint(pt, ptQ);
|
|
AddPoint(pt);
|
|
end;
|
|
|
|
procedure AppendPath(const path: TPathD);
|
|
var
|
|
len: integer;
|
|
begin
|
|
len := Length(path);
|
|
if resCnt + len > resCap then
|
|
begin
|
|
inc(resCap, len);
|
|
setLength(result, resCap);
|
|
end;
|
|
Move(path[0], result[resCnt], len * SizeOf(TPointD));
|
|
inc(resCnt, len);
|
|
end;
|
|
|
|
var
|
|
i : cardinal;
|
|
prevI : cardinal;
|
|
len : cardinal;
|
|
highI : cardinal;
|
|
iLo,iHi : cardinal;
|
|
growRec : TGrowRec;
|
|
absDelta : double;
|
|
almostNoAngle: Boolean;
|
|
begin
|
|
Result := nil;
|
|
if not Assigned(path) then exit;
|
|
len := Length(path);
|
|
if not isOpen then
|
|
while (len > 2) and
|
|
PointsNearEqual(path[len -1], path[0], 0.001) do
|
|
dec(len);
|
|
if len < 2 then Exit;
|
|
|
|
absDelta := Abs(delta);
|
|
if absDelta < MinStrokeWidth/2 then
|
|
begin
|
|
if delta < 0 then
|
|
delta := -MinStrokeWidth/2 else
|
|
delta := MinStrokeWidth/2;
|
|
end;
|
|
if absDelta < 1 then
|
|
joinStyle := jsSquare
|
|
else if joinStyle = jsAuto then
|
|
begin
|
|
if delta < AutoWidthThreshold / 2 then
|
|
joinStyle := jsSquare else
|
|
joinStyle := jsRound;
|
|
end;
|
|
|
|
if assigned(normals) then
|
|
norms := normals else
|
|
norms := GetNormals(path);
|
|
|
|
highI := len -1;
|
|
parallelOffsets := GetParallelOffests(path, norms, delta);
|
|
|
|
if joinStyle = jsRound then
|
|
begin
|
|
growRec.Radius := delta;
|
|
growRec.StepsPerRad := CalcRoundingSteps(growRec.Radius)/(Pi *2);
|
|
if delta < 0 then
|
|
GetSinCos(-1/growRec.StepsPerRad, growRec.StepSin, growRec.StepCos) else
|
|
GetSinCos(1/growRec.StepsPerRad, growRec.StepSin, growRec.StepCos);
|
|
end else
|
|
begin
|
|
if miterLim <= 0 then miterLim := DefaultMiterLimit
|
|
else if miterLim < 2 then miterLim := 2;
|
|
miterLim := 2 /(sqr(miterLim));
|
|
growRec.StepsPerRad := 0; //stop compiler warning.
|
|
end;
|
|
|
|
resCnt := 0; resCap := 0;
|
|
|
|
if isOpen then
|
|
begin
|
|
iLo := 1; iHi := highI -1;
|
|
prevI := 0;
|
|
AddPoint(parallelOffsets[0]);
|
|
end else
|
|
begin
|
|
iLo := 0; iHi := highI;
|
|
prevI := highI;
|
|
end;
|
|
|
|
for i := iLo to iHi do
|
|
begin
|
|
|
|
if PointsNearEqual(path[i], path[prevI], 0.01) then
|
|
begin
|
|
prevI := i;
|
|
Continue;
|
|
end;
|
|
|
|
growRec.aSin := CrossProduct(norms[prevI], norms[i]);
|
|
growRec.aCos := DotProduct(norms[prevI], norms[i]);
|
|
|
|
almostNoAngle := ValueAlmostZero(growRec.aCos -1);
|
|
if almostNoAngle or ((growRec.aSin * delta < 0)) then
|
|
begin //ie is concave
|
|
AddPoint(parallelOffsets[prevI*2+1]);
|
|
AddPoint(parallelOffsets[i*2]);
|
|
end
|
|
else if (joinStyle = jsRound) and
|
|
(Abs(growRec.aSin) > 0.08) then //only round if angle > ~5 deg
|
|
begin
|
|
AppendPath(DoRound(path[i], norms[prevI], growRec));
|
|
end
|
|
else if (joinStyle = jsMiter) then // nb: miterLim <= 2
|
|
begin
|
|
if (1 + growRec.aCos > miterLim) then //within miter range
|
|
DoMiter(i, prevI, growRec.aCos) else
|
|
DoSquare(i, prevI);
|
|
end
|
|
// don't bother squaring angles that deviate < ~20 deg. because squaring
|
|
// will be indistinguishable from mitering and just be a lot slower
|
|
else if (growRec.aCos > 0.9) then
|
|
DoMiter(i, prevI, growRec.aCos)
|
|
else
|
|
DoSquare(i, prevI);
|
|
|
|
prevI := i;
|
|
end;
|
|
if isOpen then AddPoint(parallelOffsets[highI*2-1]);
|
|
SetLength(Result, resCnt);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
procedure AppendPath(var path: TPathD; const pt: TPointD);
|
|
var
|
|
len: integer;
|
|
begin
|
|
len := length(path);
|
|
if (len > 0) and PointsEqual(pt, path[len -1]) then Exit;
|
|
setLength(path, len + 1);
|
|
path[len] := pt;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
procedure AppendPath(var path1: TPathD; const path2: TPathD);
|
|
var
|
|
len1, len2: integer;
|
|
begin
|
|
len1 := length(path1);
|
|
len2 := length(path2);
|
|
if len2 = 0 then Exit;
|
|
if (len1 > 0) and PointsEqual(path2[0], path1[len1 -1]) then dec(len1);
|
|
setLength(path1, len1 + len2);
|
|
Move(path2[0], path1[len1], len2 * SizeOf(TPointD));
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
procedure AppendPoint(var path: TPathD; const extra: TPointD);
|
|
var
|
|
len: integer;
|
|
begin
|
|
len := length(path);
|
|
SetLength(path, len +1);
|
|
path[len] := extra;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
procedure AppendPath(var paths: TPathsD;
|
|
const extra: TPathD);
|
|
var
|
|
len1, len2: integer;
|
|
begin
|
|
len2 := length(extra);
|
|
if len2 = 0 then Exit;
|
|
len1 := length(paths);
|
|
setLength(paths, len1 + 1);
|
|
paths[len1] := Copy(extra, 0, len2);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
procedure AppendPath(var paths: TPathsD;
|
|
const extra: TPathsD);
|
|
var
|
|
i, len1, len2: integer;
|
|
begin
|
|
len2 := length(extra);
|
|
if len2 = 0 then Exit;
|
|
len1 := length(paths);
|
|
setLength(paths, len1 + len2);
|
|
for i := 0 to len2 -1 do
|
|
paths[len1+i] := Copy(extra[i], 0, length(extra[i]));
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
procedure AppendPath(var ppp: TArrayOfPathsD; const extra: TPathsD);
|
|
var
|
|
len: integer;
|
|
begin
|
|
len := length(ppp);
|
|
setLength(ppp, len + 1);
|
|
if Assigned(extra) then
|
|
AppendPath(ppp[len], extra) else
|
|
ppp[len] := nil;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
procedure RotatePoint(var pt: TPointD;
|
|
const focalPoint: TPointD; sinA, cosA: double);
|
|
var
|
|
tmpX, tmpY: double;
|
|
begin
|
|
tmpX := pt.X-focalPoint.X;
|
|
tmpY := pt.Y-focalPoint.Y;
|
|
pt.X := tmpX * cosA - tmpY * sinA + focalPoint.X;
|
|
pt.Y := tmpX * sinA + tmpY * cosA + focalPoint.Y;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
procedure RotatePoint(var pt: TPointD;
|
|
const focalPoint: TPointD; angleRad: double);
|
|
var
|
|
sinA, cosA: double;
|
|
begin
|
|
if angleRad = 0 then Exit;
|
|
if not ClockwiseRotationIsAnglePositive then angleRad := -angleRad;
|
|
GetSinCos(angleRad, sinA, cosA);
|
|
RotatePoint(pt, focalPoint, sinA, cosA);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function RotatePathInternal(const path: TPathD;
|
|
const focalPoint: TPointD; sinA, cosA: double): TPathD;
|
|
var
|
|
i: integer;
|
|
x,y: double;
|
|
begin
|
|
SetLength(Result, length(path));
|
|
for i := 0 to high(path) do
|
|
begin
|
|
x := path[i].X - focalPoint.X;
|
|
y := path[i].Y - focalPoint.Y;
|
|
Result[i].X := x * cosA - y * sinA + focalPoint.X;
|
|
Result[i].Y := x * sinA + y * cosA + focalPoint.Y;
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function RotatePath(const path: TPathD;
|
|
const focalPoint: TPointD; angleRads: double): TPathD;
|
|
var
|
|
sinA, cosA: double;
|
|
begin
|
|
if angleRads = 0 then
|
|
begin
|
|
Result := path;
|
|
Exit;
|
|
end;
|
|
if not ClockwiseRotationIsAnglePositive then angleRads := -angleRads;
|
|
GetSinCos(angleRads, sinA, cosA);
|
|
Result := RotatePathInternal(path, focalPoint, sinA, cosA);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function RotatePath(const paths: TPathsD;
|
|
const focalPoint: TPointD; angleRads: double): TPathsD;
|
|
var
|
|
i: integer;
|
|
sinA, cosA: double;
|
|
fp: TPointD;
|
|
begin
|
|
Result := paths;
|
|
if not IsValid(angleRads) then Exit;
|
|
NormalizeAngle(angleRads);
|
|
if angleRads = 0 then Exit;
|
|
if not ClockwiseRotationIsAnglePositive then
|
|
angleRads := -angleRads;
|
|
GetSinCos(angleRads, sinA, cosA);
|
|
SetLength(Result, length(paths));
|
|
if IsValid(focalPoint) then
|
|
fp := focalPoint else
|
|
fp := GetBoundsD(paths).MidPoint;
|
|
for i := 0 to high(paths) do
|
|
Result[i] := RotatePathInternal(paths[i], fp, sinA, cosA);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetAngle(const origin, pt: TPoint): double;
|
|
var
|
|
x,y: double;
|
|
begin
|
|
x := pt.X - origin.X;
|
|
y := pt.Y - origin.Y;
|
|
if x = 0 then
|
|
begin
|
|
if y > 0 then result := angle90
|
|
else result := -angle90;
|
|
end
|
|
else if y = 0 then
|
|
begin
|
|
if x > 0 then result := 0
|
|
else result := angle180;
|
|
end else
|
|
result := arctan2(y, x); //range between -Pi and Pi
|
|
if not ClockwiseRotationIsAnglePositive then Result := -Result;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetAngle(const origin, pt: TPointD): double;
|
|
var
|
|
x,y: double;
|
|
begin
|
|
x := pt.X - origin.X;
|
|
y := pt.Y - origin.Y;
|
|
if x = 0 then
|
|
begin
|
|
if y > 0 then result := angle90
|
|
else result := -angle90;
|
|
end
|
|
else if y = 0 then
|
|
begin
|
|
if x > 0 then result := 0
|
|
else result := angle180;
|
|
end else
|
|
result := arctan2(y, x); //range between -Pi and Pi
|
|
if not ClockwiseRotationIsAnglePositive then Result := -Result;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetAngle(const a, b, c: TPoint): double;
|
|
var
|
|
ab, bc: TPointD;
|
|
dp, cp: double;
|
|
begin
|
|
//https://stackoverflow.com/a/3487062/359538
|
|
ab := PointD(b.x - a.x, b.y - a.y);
|
|
bc := PointD(b.x - c.x, b.y - c.y);
|
|
dp := (ab.x * bc.x + ab.y * bc.y);
|
|
cp := (ab.x * bc.y - ab.y * bc.x);
|
|
Result := arctan2(cp, dp); //range between -Pi and Pi
|
|
if not ClockwiseRotationIsAnglePositive then Result := -Result;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetAngle(const a, b, c: TPointD): double;
|
|
var
|
|
ab, bc: TPointD;
|
|
dp, cp: double;
|
|
begin
|
|
//https://stackoverflow.com/a/3487062/359538
|
|
ab := PointD(b.x - a.x, b.y - a.y);
|
|
bc := PointD(b.x - c.x, b.y - c.y);
|
|
dp := (ab.x * bc.x + ab.y * bc.y);
|
|
cp := (ab.x * bc.y - ab.y * bc.x);
|
|
Result := arctan2(cp, dp); //range between -Pi and Pi
|
|
if not ClockwiseRotationIsAnglePositive then Result := -Result;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetPointAtAngleAndDist(const origin: TPointD;
|
|
angle, distance: double): TPointD;
|
|
begin
|
|
Result := origin;
|
|
Result.X := Result.X + distance;
|
|
RotatePoint(Result, origin, angle);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function IntersectPoint(const ln1a, ln1b, ln2a, ln2b: TPointD): TPointD;
|
|
var
|
|
m1,b1,m2,b2: double;
|
|
begin
|
|
result := InvalidPointD;
|
|
//see http://astronomy.swin.edu.au/~pbourke/geometry/lineline2d/
|
|
if (ln1B.X = ln1A.X) then
|
|
begin
|
|
if (ln2B.X = ln2A.X) then exit; //parallel lines
|
|
m2 := (ln2B.Y - ln2A.Y)/(ln2B.X - ln2A.X);
|
|
b2 := ln2A.Y - m2 * ln2A.X;
|
|
Result.X := ln1A.X;
|
|
Result.Y := m2*ln1A.X + b2;
|
|
end
|
|
else if (ln2B.X = ln2A.X) then
|
|
begin
|
|
m1 := (ln1B.Y - ln1A.Y)/(ln1B.X - ln1A.X);
|
|
b1 := ln1A.Y - m1 * ln1A.X;
|
|
Result.X := ln2A.X;
|
|
Result.Y := m1*ln2A.X + b1;
|
|
end else
|
|
begin
|
|
m1 := (ln1B.Y - ln1A.Y)/(ln1B.X - ln1A.X);
|
|
b1 := ln1A.Y - m1 * ln1A.X;
|
|
m2 := (ln2B.Y - ln2A.Y)/(ln2B.X - ln2A.X);
|
|
b2 := ln2A.Y - m2 * ln2A.X;
|
|
if m1 = m2 then exit; //parallel lines
|
|
Result.X := (b2 - b1)/(m1 - m2);
|
|
Result.Y := m1 * Result.X + b1;
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function IntersectPoint(const ln1a, ln1b, ln2a, ln2b: TPointD;
|
|
out ip: TPointD): Boolean;
|
|
begin
|
|
ip := IntersectPoint(ln1a, ln1b, ln2a, ln2b);
|
|
Result := IsValid(ip);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function SegmentIntersectPt(const ln1a, ln1b, ln2a, ln2b: TPointD): TPointD;
|
|
var
|
|
pqd,r,s : TPointD; //scalar vectors;
|
|
rs, t : double;
|
|
begin
|
|
//https://stackoverflow.com/a/565282/359538
|
|
Result := InvalidPointD;
|
|
r := PointD(ln1b.X - ln1a.X, ln1b.Y - ln1a.Y);
|
|
s := PointD(ln2b.X - ln2a.X, ln2b.Y - ln2a.Y);
|
|
rs := CrossProduct(r,s);
|
|
if Abs(rs) < 1 then Exit;
|
|
pqd.X := ln2a.X - ln1a.X;
|
|
pqd.y := ln2a.Y - ln1a.Y;
|
|
t := CrossProduct(pqd, s) / rs;
|
|
if (t < -0.025) or (t > 1.025) then Exit;
|
|
Result.X := ln1a.X + t * r.X;
|
|
Result.Y := ln1a.Y + t * r.Y;
|
|
// pqd.X := -pqd.X; pqd.Y := -pqd.Y;
|
|
// u := CrossProduct(pqd, r) / rs;
|
|
// if (u < -0.05) or (u > 1.05) then Exit;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function SegmentsIntersect(const ln1a, ln1b, ln2a, ln2b: TPointD;
|
|
out ip: TPointD): Boolean;
|
|
begin
|
|
ip := SegmentIntersectPt(ln1a, ln1b, ln2a, ln2b);
|
|
Result := IsValid(ip);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function ReverseNormals(const norms: TPathD): TPathD;
|
|
var
|
|
i, highI: integer;
|
|
begin
|
|
highI := high(norms);
|
|
setLength(result, highI +1);
|
|
for i := 1 to highI do
|
|
begin
|
|
result[i -1].X := -norms[highI -i].X;
|
|
result[i -1].Y := -norms[highI -i].Y;
|
|
end;
|
|
result[highI].X := -norms[highI].X;
|
|
result[highI].Y := -norms[highI].Y;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GrowOpenLine(const line: TPathD; width: double;
|
|
joinStyle: TJoinStyle; endStyle: TEndStyle;
|
|
miterLimOrRndScale: double): TPathD;
|
|
var
|
|
len, x,y: integer;
|
|
segLen, halfWidth: double;
|
|
normals, lineL, lineR, arc: TPathD;
|
|
invNorm: TPointD;
|
|
growRec: TGrowRec;
|
|
begin
|
|
Result := nil;
|
|
len := length(line);
|
|
if len = 0 then Exit;
|
|
if width < MinStrokeWidth then
|
|
width := MinStrokeWidth;
|
|
halfWidth := width * 0.5;
|
|
if len = 1 then
|
|
begin
|
|
x := Round(line[0].X);
|
|
y := Round(line[0].Y);
|
|
SetLength(result, 1);
|
|
result := Ellipse(RectD(x -halfWidth, y -halfWidth,
|
|
x +halfWidth, y +halfWidth));
|
|
Exit;
|
|
end;
|
|
|
|
//with very narrow lines, don't get fancy with joins and line ends
|
|
if (width <= 2) then
|
|
begin
|
|
joinStyle := jsSquare;
|
|
if endStyle = esRound then endStyle := esSquare;
|
|
end
|
|
else if joinStyle = jsAuto then
|
|
begin
|
|
if (endStyle = esRound) and
|
|
(width >= AutoWidthThreshold) then
|
|
joinStyle := jsRound
|
|
else
|
|
joinStyle := jsSquare;
|
|
end;
|
|
|
|
normals := GetNormals(line);
|
|
if endStyle = esRound then
|
|
begin
|
|
//get the rounding parameters
|
|
growRec.StepsPerRad :=
|
|
CalcRoundingSteps(halfWidth * miterLimOrRndScale)/(Pi*2);
|
|
GetSinCos(1/growRec.StepsPerRad, growRec.StepSin, growRec.StepCos);
|
|
growRec.Radius := halfWidth;
|
|
|
|
//grow the line's left side of the line => line1
|
|
lineL := Grow(line, normals,
|
|
halfWidth, joinStyle, miterLimOrRndScale, true);
|
|
//build the rounding at the start => result
|
|
invNorm.X := -normals[0].X;
|
|
invNorm.Y := -normals[0].Y;
|
|
growRec.aSin := invNorm.X * normals[0].Y - invNorm.Y * normals[0].X;
|
|
growRec.aCos := invNorm.X * normals[0].X + invNorm.Y * normals[0].Y;
|
|
Result := DoRound(line[0], invNorm, growRec);
|
|
//join line1 into result
|
|
AppendPath(Result, lineL);
|
|
//reverse the normals and build the end arc => arc
|
|
normals := ReverseNormals(normals);
|
|
invNorm.X := -normals[0].X; invNorm.Y := -normals[0].Y;
|
|
growRec.aSin := invNorm.X * normals[0].Y - invNorm.Y * normals[0].X;
|
|
growRec.aCos := invNorm.X * normals[0].X + invNorm.Y * normals[0].Y;
|
|
arc := DoRound(line[High(line)], invNorm, growRec);
|
|
//grow the line's right side of the line
|
|
lineR := Grow(ReversePath(line), normals,
|
|
halfWidth, joinStyle, miterLimOrRndScale, true);
|
|
//join arc and line2 into result
|
|
AppendPath(Result, arc);
|
|
AppendPath(Result, lineR);
|
|
end else
|
|
begin
|
|
lineL := Copy(line, 0, len);
|
|
if endStyle = esSquare then
|
|
begin
|
|
// esSquare => extends both line ends by 1/2 lineWidth
|
|
AdjustPoint(lineL[0], lineL[1], width * 0.5);
|
|
AdjustPoint(lineL[len-1], lineL[len-2], width * 0.5);
|
|
end else
|
|
begin
|
|
//esButt -> extend only very short end segments
|
|
segLen := Distance(lineL[0], lineL[1]);
|
|
if segLen < width * 0.5 then
|
|
AdjustPoint(lineL[0], lineL[1], width * 0.5 - segLen);
|
|
segLen := Distance(lineL[len-1], lineL[len-2]);
|
|
if segLen < width * 0.5 then
|
|
AdjustPoint(lineL[len-1], lineL[len-2], width * 0.5 - segLen);
|
|
end;
|
|
//first grow the left side of the line => Result
|
|
Result := Grow(lineL, normals,
|
|
halfWidth, joinStyle, miterLimOrRndScale, true);
|
|
//reverse normals and path and grow the right side => lineR
|
|
normals := ReverseNormals(normals);
|
|
lineR := Grow(ReversePath(lineL), normals,
|
|
halfWidth, joinStyle, miterLimOrRndScale, true);
|
|
//join both sides
|
|
AppendPath(Result, lineR);
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GrowClosedLine(const line: TPathD; width: double;
|
|
joinStyle: TJoinStyle; miterLimOrRndScale: double): TPathsD;
|
|
var
|
|
line2, norms: TPathD;
|
|
rec: TRectD;
|
|
skipHole: Boolean;
|
|
begin
|
|
rec := GetBoundsD(line);
|
|
skipHole := (rec.Width <= width) or (rec.Height <= width);
|
|
if skipHole then
|
|
begin
|
|
SetLength(Result, 1);
|
|
norms := GetNormals(line);
|
|
Result[0] := Grow(line, norms, width/2, joinStyle, miterLimOrRndScale);
|
|
end else
|
|
begin
|
|
SetLength(Result, 2);
|
|
norms := GetNormals(line);
|
|
Result[0] := Grow(line, norms, width/2, joinStyle, miterLimOrRndScale);
|
|
line2 := ReversePath(line);
|
|
norms := ReverseNormals(norms);
|
|
Result[1] := Grow(line2, norms, width/2, joinStyle, miterLimOrRndScale);
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Outline(const line: TPathD; lineWidth: double;
|
|
joinStyle: TJoinStyle; endStyle: TEndStyle;
|
|
miterLimOrRndScale: double): TPathsD;
|
|
begin
|
|
if not assigned(line) then
|
|
Result := nil
|
|
else if endStyle = esClosed then
|
|
result := GrowClosedLine(line,
|
|
lineWidth, joinStyle, miterLimOrRndScale)
|
|
else
|
|
begin
|
|
SetLength(Result,1);
|
|
result[0] := GrowOpenLine(line, lineWidth,
|
|
joinStyle, endStyle, miterLimOrRndScale);
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Outline(const lines: TPathsD; lineWidth: double;
|
|
joinStyle: TJoinStyle; endStyle: TEndStyle;
|
|
miterLimOrRndScale: double): TPathsD;
|
|
var
|
|
i: integer;
|
|
begin
|
|
result := nil;
|
|
if not assigned(lines) then exit;
|
|
if joinStyle = jsAuto then
|
|
begin
|
|
if endStyle in [esPolygon, esRound] then
|
|
joinStyle := jsRound else
|
|
joinStyle := jsSquare;
|
|
end;
|
|
if endStyle = esPolygon then
|
|
for i := 0 to high(lines) do
|
|
AppendPath(Result, GrowClosedLine(lines[i],
|
|
lineWidth, joinStyle, miterLimOrRndScale))
|
|
else
|
|
for i := 0 to high(lines) do
|
|
AppendPath(Result, GrowOpenLine(lines[i], lineWidth,
|
|
joinStyle, endStyle, miterLimOrRndScale));
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Rectangle(const rec: TRect): TPathD;
|
|
begin
|
|
setLength(Result, 4);
|
|
with rec do
|
|
begin
|
|
result[0] := PointD(left, top);
|
|
result[1] := PointD(right, top);
|
|
result[2] := PointD(right, bottom);
|
|
result[3] := PointD(left, bottom);
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Rectangle(const rec: TRectD): TPathD;
|
|
begin
|
|
setLength(Result, 4);
|
|
with rec do
|
|
begin
|
|
result[0] := PointD(left, top);
|
|
result[1] := PointD(right, top);
|
|
result[2] := PointD(right, bottom);
|
|
result[3] := PointD(left, bottom);
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Rectangle(l, t, r, b: double): TPathD;
|
|
begin
|
|
setLength(Result, 4);
|
|
result[0] := PointD(l, t);
|
|
result[1] := PointD(r, t);
|
|
result[2] := PointD(r, b);
|
|
result[3] := PointD(l, b);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
procedure InflateRect(var rec: TRect; dx, dy: integer);
|
|
begin
|
|
rec.Left := rec.Left - dx;
|
|
rec.Top := rec.Top - dy;
|
|
rec.Right := rec.Right + dx;
|
|
rec.Bottom := rec.Bottom + dy;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
procedure InflateRect(var rec: TRectD; dx, dy: double);
|
|
begin
|
|
rec.Left := rec.Left - dx;
|
|
rec.Top := rec.Top - dy;
|
|
rec.Right := rec.Right + dx;
|
|
rec.Bottom := rec.Bottom + dy;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function NormalizeRect(var rect: TRect): Boolean;
|
|
var
|
|
i: integer;
|
|
begin
|
|
Result := False;
|
|
with rect do
|
|
begin
|
|
if Left > Right then
|
|
begin
|
|
i := Left;
|
|
Left := Right;
|
|
Right := i;
|
|
Result := True;
|
|
end;
|
|
if Top > Bottom then
|
|
begin
|
|
i := Top;
|
|
Top := Bottom;
|
|
Bottom := i;
|
|
Result := True;
|
|
end;
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function RoundRect(const rec: TRect; radius: integer): TPathD;
|
|
begin
|
|
Result := RoundRect(RectD(rec), PointD(radius, radius));
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function RoundRect(const rec: TRect; radius: TPoint): TPathD;
|
|
begin
|
|
Result := RoundRect(RectD(rec), PointD(radius));
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function RoundRect(const rec: TRectD; radius: double): TPathD;
|
|
begin
|
|
Result := RoundRect(rec, PointD(radius, radius));
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function RoundRect(const rec: TRectD; radius: TPointD): TPathD;
|
|
var
|
|
i,j : integer;
|
|
corners : TPathD;
|
|
bezPts : TPathD;
|
|
magic : TPointD;
|
|
const
|
|
magicC: double = 0.55228475; // =4/3 * (sqrt(2)-1)
|
|
begin
|
|
Result := nil;
|
|
if rec.IsEmpty then Exit;
|
|
radius.X := Min(radius.X, rec.Width/2);
|
|
radius.Y := Min(radius.Y, rec.Height/2);
|
|
if (radius.X < 1) and (radius.Y < 1) then
|
|
begin
|
|
Result := Rectangle(rec);
|
|
Exit;
|
|
end;
|
|
magic.X := radius.X * magicC;
|
|
magic.Y := radius.Y * magicC;
|
|
SetLength(Corners, 4);
|
|
with rec do
|
|
begin
|
|
corners[0] := PointD(Right, Top);
|
|
corners[1] := BottomRight;
|
|
corners[2] := PointD(Left, Bottom);
|
|
corners[3] := TopLeft;
|
|
end;
|
|
SetLength(Result, 1);
|
|
Result[0].X := corners[3].X + radius.X;
|
|
Result[0].Y := corners[3].Y;
|
|
SetLength(bezPts, 4);
|
|
for i := 0 to High(corners) do
|
|
begin
|
|
for j := 0 to 3 do bezPts[j] := corners[i];
|
|
case i of
|
|
3:
|
|
begin
|
|
bezPts[0].Y := bezPts[0].Y + radius.Y;
|
|
bezPts[1].Y := bezPts[0].Y - magic.Y;
|
|
bezPts[3].X := bezPts[3].X + radius.X;
|
|
bezPts[2].X := bezPts[3].X - magic.X;
|
|
end;
|
|
0:
|
|
begin
|
|
bezPts[0].X := bezPts[0].X - radius.X;
|
|
bezPts[1].X := bezPts[0].X + magic.X;
|
|
bezPts[3].Y := bezPts[3].Y + radius.Y;
|
|
bezPts[2].Y := bezPts[3].Y - magic.Y;
|
|
end;
|
|
1:
|
|
begin
|
|
bezPts[0].Y := bezPts[0].Y - radius.Y;
|
|
bezPts[1].Y := bezPts[0].Y + magic.Y;
|
|
bezPts[3].X := bezPts[3].X - radius.X;
|
|
bezPts[2].X := bezPts[3].X + magic.X;
|
|
end;
|
|
2:
|
|
begin
|
|
bezPts[0].X := bezPts[0].X + radius.X;
|
|
bezPts[1].X := bezPts[0].X - magic.X;
|
|
bezPts[3].Y := bezPts[3].Y - radius.Y;
|
|
bezPts[2].Y := bezPts[3].Y + magic.Y;
|
|
end;
|
|
end;
|
|
AppendPath(Result, FlattenCBezier(bezPts));
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Circle(const pt: TPoint; radius: double): TPathD;
|
|
var
|
|
rec: TRectD;
|
|
begin
|
|
rec.Left := pt.X - radius;
|
|
rec.Right := pt.X + radius;
|
|
rec.Top := pt.Y - radius;
|
|
rec.Bottom := pt.Y + radius;
|
|
Result := Ellipse(rec);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Circle(const pt: TPointD; radius: double): TPathD;
|
|
var
|
|
rec: TRectD;
|
|
begin
|
|
rec.Left := pt.X - radius;
|
|
rec.Right := pt.X + radius;
|
|
rec.Top := pt.Y - radius;
|
|
rec.Bottom := pt.Y + radius;
|
|
Result := Ellipse(rec);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Circle(const pt: TPointD; radius: double; pendingScale: double): TPathD;
|
|
var
|
|
rec: TRectD;
|
|
begin
|
|
rec.Left := pt.X - radius;
|
|
rec.Right := pt.X + radius;
|
|
rec.Top := pt.Y - radius;
|
|
rec.Bottom := pt.Y + radius;
|
|
Result := Ellipse(rec, pendingScale);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Ellipse(const rec: TRectD; pendingScale: double): TPathD;
|
|
var
|
|
steps: integer;
|
|
begin
|
|
if pendingScale <= 0 then pendingScale := 1;
|
|
steps := Round(CalcRoundingSteps((rec.width + rec.Height) * pendingScale));
|
|
Result := Ellipse(rec, steps);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
function Ellipse(const rec: TRect; steps: integer): TPathD;
|
|
begin
|
|
Result := Ellipse(RectD(rec), steps);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Ellipse(const rec: TRectD; steps: integer): TPathD;
|
|
var
|
|
i: Integer;
|
|
sinA, cosA: double;
|
|
centre, radius, delta: TPointD;
|
|
begin
|
|
result := nil;
|
|
if rec.IsEmpty then Exit;
|
|
with rec do
|
|
begin
|
|
centre := rec.MidPoint;
|
|
radius := PointD(Width * 0.5, Height * 0.5);
|
|
end;
|
|
if steps < 4 then
|
|
steps := Round(CalcRoundingSteps(rec.width + rec.height));
|
|
GetSinCos(2 * Pi / Steps, sinA, cosA);
|
|
delta.x := cosA; delta.y := sinA;
|
|
SetLength(Result, Steps);
|
|
Result[0] := PointD(centre.X + radius.X, centre.Y);
|
|
for i := 1 to steps -1 do
|
|
begin
|
|
Result[i] := PointD(centre.X + radius.X * delta.x,
|
|
centre.Y + radius.y * delta.y);
|
|
delta := PointD(delta.X * cosA - delta.Y * sinA,
|
|
delta.Y * cosA + delta.X * sinA);
|
|
end; //rotates clockwise
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function RotatedEllipse(const rec: TRectD; angle: double; steps: integer = 0): TPathD;
|
|
begin
|
|
Result := Ellipse(rec, steps);
|
|
if angle = 0 then Exit;
|
|
Result := RotatePath(Result, rec.MidPoint, angle);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function RotatedEllipse(const rec: TRectD; angle: double; pendingScale: double): TPathD;
|
|
begin
|
|
Result := Ellipse(rec, pendingScale);
|
|
if angle = 0 then Exit;
|
|
Result := RotatePath(Result, rec.MidPoint, angle);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function AngleToEllipticalAngle(const ellRec: TRectD; angle: double): double;
|
|
begin
|
|
Result := arctan2(ellRec.Height/ellRec.Width * sin(angle), cos(angle));
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function EllipticalAngleToAngle(const ellRec: TRectD; angle: double): double;
|
|
begin
|
|
Result := ArcTan2(sin(angle) *ellRec.Width, cos(angle) * ellRec.Height);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Star(const rec: TRectD; points: integer; indentFrac: double): TPathD;
|
|
var
|
|
i: integer;
|
|
innerOff: double;
|
|
p, p2: TPathD;
|
|
rec2: TRectD;
|
|
begin
|
|
Result := nil;
|
|
if points < 5 then points := 5
|
|
else if points > 15 then points := 15;
|
|
if indentFrac < 0.2 then indentFrac := 0.2
|
|
else if indentFrac > 0.8 then indentFrac := 0.8;
|
|
innerOff := Min(rec.Width, rec.Height) * indentFrac * 0.5;
|
|
if not Odd(points) then inc(points);
|
|
p := Ellipse(rec, points);
|
|
if not Assigned(p) then Exit;
|
|
rec2 := rec;
|
|
Img32.Vector.InflateRect(rec2, -innerOff, -innerOff);
|
|
if rec2.IsEmpty then
|
|
p2 := Ellipse(rec, points*2) else
|
|
p2 := Ellipse(rec2, points*2);
|
|
SetLength(Result, points*2);
|
|
for i := 0 to points -1 do
|
|
begin
|
|
Result[i*2] := p[i];
|
|
Result[i*2+1] := p2[i*2+1];
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Star(const focalPt: TPointD;
|
|
innerRadius, outerRadius: double; points: integer): TPathD;
|
|
var
|
|
i: Integer;
|
|
sinA, cosA: double;
|
|
delta: TPointD;
|
|
begin
|
|
result := nil;
|
|
if (innerRadius <= 0) or (outerRadius <= 0) then Exit;
|
|
if points <= 5 then points := 10
|
|
else points := points * 2;
|
|
GetSinCos(2 * Pi / points, sinA, cosA);
|
|
delta.x := cosA; delta.y := sinA;
|
|
SetLength(Result, points);
|
|
Result[0] := PointD(focalPt.X + innerRadius, focalPt.Y);
|
|
for i := 1 to points -1 do
|
|
begin
|
|
if Odd(i) then
|
|
Result[i] := PointD(focalPt.X + outerRadius * delta.x,
|
|
focalPt.Y + outerRadius * delta.y)
|
|
else
|
|
Result[i] := PointD(focalPt.X + innerRadius * delta.x,
|
|
focalPt.Y + innerRadius * delta.y);
|
|
delta := PointD(delta.X * cosA - delta.Y * sinA,
|
|
delta.Y * cosA + delta.X * sinA);
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Arc(const rec: TRectD;
|
|
startAngle, endAngle: double; scale: double): TPathD;
|
|
var
|
|
i, steps: Integer;
|
|
angle: double;
|
|
sinA, cosA: double;
|
|
centre, radius: TPointD;
|
|
deltaX, deltaX2, deltaY: double;
|
|
const
|
|
qtrDeg = PI/1440;
|
|
begin
|
|
Result := nil;
|
|
if (endAngle = startAngle) or IsEmptyRect(rec) then Exit;
|
|
if scale <= 0 then scale := 4.0;
|
|
if not ClockwiseRotationIsAnglePositive then
|
|
begin
|
|
startAngle := -startAngle;
|
|
endAngle := -endAngle;
|
|
end;
|
|
NormalizeAngle(startAngle, qtrDeg);
|
|
NormalizeAngle(endAngle, qtrDeg);
|
|
with rec do
|
|
begin
|
|
centre := MidPoint;
|
|
radius := PointD(Width * 0.5, Height * 0.5);
|
|
end;
|
|
if endAngle < startAngle then
|
|
angle := endAngle - startAngle + angle360 else
|
|
angle := endAngle - startAngle;
|
|
//steps = (No. steps for a whole ellipse) * angle/(2*Pi)
|
|
steps := Round(CalcRoundingSteps((rec.width + rec.height) * scale));
|
|
steps := steps div 2; /////////////////////////////////
|
|
if steps < 2 then steps := 2;
|
|
SetLength(Result, Steps +1);
|
|
//angle of the first step ...
|
|
GetSinCos(startAngle, deltaY, deltaX);
|
|
Result[0].X := centre.X + radius.X * deltaX;
|
|
Result[0].Y := centre.Y + radius.y * deltaY;
|
|
//angle of each subsequent step ...
|
|
GetSinCos(angle / Steps, sinA, cosA);
|
|
for i := 1 to steps do
|
|
begin
|
|
deltaX2 := deltaX * cosA - deltaY * sinA;
|
|
deltaY := deltaY * cosA + deltaX * sinA;
|
|
deltaX := deltaX2;
|
|
Result[i].X := centre.X + radius.X * deltaX;
|
|
Result[i].Y := centre.Y + radius.y * deltaY;
|
|
end; //progresses clockwise from start to end
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function Pie(const rec: TRectD;
|
|
StartAngle, EndAngle: double; scale: double): TPathD;
|
|
var
|
|
len: integer;
|
|
begin
|
|
result := Arc(rec, StartAngle, EndAngle, scale);
|
|
len := length(result);
|
|
setLength(result, len +1);
|
|
result[len] := PointD((rec.Left + rec.Right)/2, (rec.Top + rec.Bottom)/2);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function ArrowHead(const arrowTip, ctrlPt: TPointD; size: double;
|
|
arrowStyle: TArrowStyle): TPathD;
|
|
var
|
|
unitVec, basePt: TPointD;
|
|
sDiv40, sDiv50, sDiv60, sDiv120: double;
|
|
begin
|
|
result := nil;
|
|
sDiv40 := size * 0.40;
|
|
sDiv50 := size * 0.50;
|
|
sDiv60 := size * 0.60;
|
|
sDiv120 := sDiv60 * 2;
|
|
unitVec := GetUnitVector(ctrlPt, arrowTip);
|
|
case arrowStyle of
|
|
asNone:
|
|
Exit;
|
|
asSimple:
|
|
begin
|
|
setLength(result, 3);
|
|
basePt := OffsetPoint(arrowTip, -unitVec.X * size, -unitVec.Y * size);
|
|
result[0] := arrowTip;
|
|
result[1] := OffsetPoint(basePt, -unitVec.Y * sDiv50, unitVec.X * sDiv50);
|
|
result[2] := OffsetPoint(basePt, unitVec.Y * sDiv50, -unitVec.X * sDiv50);
|
|
end;
|
|
asFancy:
|
|
begin
|
|
setLength(result, 4);
|
|
basePt := OffsetPoint(arrowTip,
|
|
-unitVec.X * sDiv120, -unitVec.Y * sDiv120);
|
|
result[0] := OffsetPoint(basePt, -unitVec.Y *sDiv50, unitVec.X *sDiv50);
|
|
result[1] := OffsetPoint(arrowTip, -unitVec.X *size, -unitVec.Y *size);
|
|
result[2] := OffsetPoint(basePt, unitVec.Y *sDiv50, -unitVec.X *sDiv50);
|
|
result[3] := arrowTip;
|
|
end;
|
|
asDiamond:
|
|
begin
|
|
setLength(result, 4);
|
|
basePt := OffsetPoint(arrowTip, -unitVec.X * sDiv60, -unitVec.Y * sDiv60);
|
|
result[0] := arrowTip;
|
|
result[1] := OffsetPoint(basePt, -unitVec.Y * sDiv50, unitVec.X * sDiv50);
|
|
result[2] := OffsetPoint(arrowTip, -unitVec.X * sDiv120, -unitVec.Y * sDiv120);
|
|
result[3] := OffsetPoint(basePt, unitVec.Y * sDiv50, -unitVec.X * sDiv50);
|
|
end;
|
|
asCircle:
|
|
begin
|
|
basePt := OffsetPoint(arrowTip, -unitVec.X * sDiv50, -unitVec.Y * sDiv50);
|
|
with Point(basePt) do
|
|
result := Ellipse(RectD(x - sDiv50, y - sDiv50, x + sDiv50, y + sDiv50));
|
|
end;
|
|
asTail:
|
|
begin
|
|
setLength(result, 6);
|
|
basePt := OffsetPoint(arrowTip, -unitVec.X * sDiv60, -unitVec.Y * sDiv60);
|
|
result[0] := OffsetPoint(arrowTip, -unitVec.X * sDiv50, -unitVec.Y * sDiv50);
|
|
result[1] := OffsetPoint(arrowTip, -unitVec.Y * sDiv40, unitVec.X * sDiv40);
|
|
result[2] := OffsetPoint(basePt, -unitVec.Y * sDiv40, unitVec.X * sDiv40);
|
|
result[3] := OffsetPoint(arrowTip, -unitVec.X * sDiv120, -unitVec.Y * sDiv120);
|
|
result[4] := OffsetPoint(basePt, unitVec.Y * sDiv40, -unitVec.X * sDiv40);
|
|
result[5] := OffsetPoint(arrowTip, unitVec.Y * sDiv40, -unitVec.X * sDiv40);
|
|
end;
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetDefaultArrowHeadSize(lineWidth: double): double;
|
|
begin
|
|
Result := lineWidth *3 + 7;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
procedure AdjustPoint(var pt: TPointD; const referencePt: TPointD; delta: double);
|
|
var
|
|
vec: TPointD;
|
|
begin
|
|
//Positive delta moves pt away from referencePt, and
|
|
//negative delta moves pt toward referencePt.
|
|
vec := GetUnitVector(referencePt, pt);
|
|
pt.X := pt.X + (vec.X * delta);
|
|
pt.Y := pt.Y + (vec.Y * delta);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function ShortenPath(const path: TPathD;
|
|
pathEnd: TPathEnd; amount: double): TPathD;
|
|
var
|
|
len, amount2: double;
|
|
vec: TPointD;
|
|
i, highPath: integer;
|
|
begin
|
|
result := path;
|
|
highPath := high(path);
|
|
if highPath < 1 then Exit;
|
|
amount2 := amount;
|
|
if pathEnd <> peEnd then
|
|
begin
|
|
//shorten start
|
|
i := 0;
|
|
while (i < highPath) do
|
|
begin
|
|
len := Distance(result[i], result[i+1]);
|
|
if (len >= amount) then Break;
|
|
amount := amount - len;
|
|
inc(i);
|
|
end;
|
|
if i > 0 then
|
|
begin
|
|
Move(path[i], Result[0], (highPath - i +1) * SizeOf(TPointD));
|
|
dec(highPath, i);
|
|
SetLength(Result, highPath +1);
|
|
end;
|
|
if amount > 0 then
|
|
begin
|
|
vec := GetUnitVector(result[0], result[1]);
|
|
result[0].X := result[0].X + vec.X * amount;
|
|
result[0].Y := result[0].Y + vec.Y * amount;
|
|
end;
|
|
end;
|
|
if pathEnd <> peStart then
|
|
begin
|
|
//shorten end
|
|
while (highPath > 1) do
|
|
begin
|
|
len := Distance(result[highPath], result[highPath -1]);
|
|
if (len >= amount2) then Break;
|
|
amount2 := amount2 - len;
|
|
dec(highPath);
|
|
end;
|
|
SetLength(Result, highPath +1);
|
|
if amount2 > 0 then
|
|
begin
|
|
vec := GetUnitVector(result[highPath], result[highPath -1]);
|
|
result[highPath].X := result[highPath].X + vec.X * amount2;
|
|
result[highPath].Y := result[highPath].Y + vec.Y * amount2;
|
|
end;
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetDashedPath(const path: TPathD;
|
|
closed: Boolean; const pattern: TArrayOfInteger;
|
|
patternOffset: PDouble): TPathsD;
|
|
var
|
|
i, highI, paIdx: integer;
|
|
vecs, path2, dash: TPathD;
|
|
patCnt, patLen: integer;
|
|
dashCapacity, dashCnt, ptsCapacity, ptsCnt: integer;
|
|
segLen, residualPat, patOff: double;
|
|
filling: Boolean;
|
|
pt, pt2: TPointD;
|
|
|
|
procedure NewDash;
|
|
begin
|
|
if ptsCnt = 1 then ptsCnt := 0;
|
|
if ptsCnt = 0 then Exit;
|
|
if dashCnt = dashCapacity then
|
|
begin
|
|
inc(dashCapacity, BuffSize);
|
|
setLength(result, dashCapacity);
|
|
end;
|
|
result[dashCnt] := Copy(dash, 0, ptsCnt);
|
|
inc(dashCnt);
|
|
ptsCapacity := BuffSize;
|
|
setLength(dash, ptsCapacity);
|
|
ptsCnt := 0;
|
|
end;
|
|
|
|
procedure ExtendDash(const pt: TPointD);
|
|
begin
|
|
if ptsCnt = ptsCapacity then
|
|
begin
|
|
inc(ptsCapacity, BuffSize);
|
|
setLength(dash, ptsCapacity);
|
|
end;
|
|
dash[ptsCnt] := pt;
|
|
inc(ptsCnt);
|
|
end;
|
|
|
|
begin
|
|
Result := nil;
|
|
paIdx := 0;
|
|
patCnt := length(pattern);
|
|
path2 := path;
|
|
highI := high(path2);
|
|
if (highI < 1) or (patCnt = 0) then Exit;
|
|
if closed and
|
|
((path2[highI].X <> path2[0].X) or (path2[highI].Y <> path2[0].Y)) then
|
|
begin
|
|
inc(highI);
|
|
setLength(path2, highI +2);
|
|
path2[highI] := path2[0];
|
|
end;
|
|
vecs := GetVectors(path2);
|
|
if (vecs[0].X = 0) and (vecs[0].Y = 0) then Exit; //not a line
|
|
if not assigned(patternOffset) then
|
|
patOff := 0 else
|
|
patOff := patternOffset^;
|
|
patLen := 0;
|
|
for i := 0 to patCnt -1 do
|
|
inc(patLen, pattern[i]);
|
|
if patOff < 0 then
|
|
begin
|
|
patOff := patLen + patOff;
|
|
while patOff < 0 do
|
|
patOff := patOff + patLen;
|
|
end
|
|
else while patOff > patLen do
|
|
patOff := patOff - patLen;
|
|
//nb: each dash is made up of 2 or more pts
|
|
dashCnt := 0;
|
|
dashCapacity := 0;
|
|
ptsCnt := 0;
|
|
ptsCapacity := 0;
|
|
filling := true;
|
|
while patOff >= pattern[paIdx] do
|
|
begin
|
|
filling := not filling;
|
|
patOff := patOff - pattern[paIdx];
|
|
paIdx := (paIdx + 1) mod patCnt;
|
|
end;
|
|
residualPat := pattern[paIdx] - patOff;
|
|
pt := path2[0];
|
|
ExtendDash(pt);
|
|
i := 0;
|
|
while (i < highI) do
|
|
begin
|
|
segLen := Distance(pt, path2[i+1]);
|
|
if residualPat > segLen then
|
|
begin
|
|
if filling then ExtendDash(path2[i+1]);
|
|
residualPat := residualPat - segLen;
|
|
pt := path2[i+1];
|
|
inc(i);
|
|
end else
|
|
begin
|
|
pt2.X := pt.X + vecs[i].X * residualPat;
|
|
pt2.Y := pt.Y + vecs[i].Y * residualPat;
|
|
if filling then ExtendDash(pt2);
|
|
filling := not filling;
|
|
NewDash;
|
|
paIdx := (paIdx + 1) mod patCnt;
|
|
residualPat := pattern[paIdx];
|
|
pt := pt2;
|
|
ExtendDash(pt);
|
|
end;
|
|
end;
|
|
NewDash;
|
|
SetLength(Result, dashCnt);
|
|
if not assigned(patternOffset) then Exit;
|
|
patOff := 0;
|
|
for i := 0 to paIdx -1 do
|
|
patOff := patOff + pattern[i];
|
|
patternOffset^ := patOff + (pattern[paIdx] - residualPat);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetDashedOutLine(const path: TPathD;
|
|
closed: Boolean; const pattern: TArrayOfInteger;
|
|
patternOffset: PDouble; lineWidth: double;
|
|
joinStyle: TJoinStyle; endStyle: TEndStyle): TPathsD;
|
|
var
|
|
i: integer;
|
|
tmp: TPathsD;
|
|
begin
|
|
Result := nil;
|
|
for i := 0 to High(pattern) do
|
|
if pattern[i] <= 0 then pattern[i] := 1;
|
|
tmp := GetDashedPath(path, closed, pattern, patternOffset);
|
|
for i := 0 to high(tmp) do
|
|
AppendPath(Result, GrowOpenLine(tmp[i],
|
|
lineWidth, joinStyle, endStyle, 2));
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetBoundsD(const paths: TPathsD): TRectD;
|
|
var
|
|
i,j: integer;
|
|
l,t,r,b: double;
|
|
p: PPointD;
|
|
begin
|
|
l := MaxInt; t := MaxInt;
|
|
r := -MaxInt; b := -MaxInt;
|
|
for i := 0 to high(paths) do
|
|
begin
|
|
p := PPointD(paths[i]);
|
|
if not assigned(p) then Continue;
|
|
for j := 0 to high(paths[i]) do
|
|
begin
|
|
if p.x < l then l := p.x;
|
|
if p.x > r then r := p.x;
|
|
if p.y < t then t := p.y;
|
|
if p.y > b then b := p.y;
|
|
inc(p);
|
|
end;
|
|
end;
|
|
if r < l then
|
|
result := NullRectD else
|
|
result := RectD(l, t, r, b);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetBoundsD(const path: TPathD): TRectD;
|
|
var
|
|
i,highI: integer;
|
|
l,t,r,b: double;
|
|
p: PPointD;
|
|
begin
|
|
highI := High(path);
|
|
if highI < 0 then
|
|
begin
|
|
Result := NullRectD;
|
|
Exit;
|
|
end;
|
|
l := path[0].X; r := l;
|
|
t := path[0].Y; b := t;
|
|
p := PPointD(path);
|
|
for i := 1 to highI do
|
|
begin
|
|
inc(p);
|
|
if p.x < l then l := p.x;
|
|
if p.x > r then r := p.x;
|
|
if p.y < t then t := p.y;
|
|
if p.y > b then b := p.y;
|
|
end;
|
|
result := RectD(l, t, r, b);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetBounds(const path: TPathD): TRect;
|
|
var
|
|
recD: TRectD;
|
|
begin
|
|
recD := GetBoundsD(path);
|
|
Result := Rect(recD);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetBounds(const paths: TPathsD): TRect;
|
|
var
|
|
recD: TRectD;
|
|
begin
|
|
recD := GetBoundsD(paths);
|
|
Result := Rect(recD);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function PrePendPoint(const pt: TPointD; const p: TPathD): TPathD;
|
|
var
|
|
len: integer;
|
|
begin
|
|
len := Length(p);
|
|
SetLength(Result, len +1);
|
|
Result[0] := pt;
|
|
if len > 0 then Move(p[0], Result[1], len * SizeOf(TPointD));
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function PrePendPoints(const pt1, pt2: TPointD; const p: TPathD): TPathD;
|
|
var
|
|
len: integer;
|
|
begin
|
|
len := Length(p);
|
|
SetLength(Result, len +2);
|
|
Result[0] := pt1;
|
|
Result[1] := pt2;
|
|
if len > 0 then Move(p[0], Result[2], len * SizeOf(TPointD));
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetPointInQuadBezier(const a,b,c: TPointD; t: double): TPointD;
|
|
var
|
|
omt: double;
|
|
begin
|
|
if t > 1 then t := 1
|
|
else if t < 0 then t := 0;
|
|
omt := 1 - t;
|
|
Result.X := a.X*omt*omt + b.X*2*omt*t + c.X*t*t;
|
|
Result.Y := a.Y*omt*omt + b.Y*2*omt*t + c.Y*t*t;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function FlattenQBezier(const firstPt: TPointD; const pts: TPathD;
|
|
tolerance: double = 0.0): TPathD; overload;
|
|
begin
|
|
if tolerance <= 0.0 then tolerance := BezierTolerance;
|
|
Result := FlattenQBezier(PrePendPoint(firstPt, pts), tolerance);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function FlattenQBezier(const pts: TPathD; tolerance: double = 0.0): TPathD;
|
|
var
|
|
i, highI: integer;
|
|
p: TPathD;
|
|
begin
|
|
Result := nil;
|
|
highI := high(pts);
|
|
if highI < 0 then Exit;
|
|
if (highI < 2) or Odd(highI) then
|
|
raise Exception.Create(rsInvalidQBezier);
|
|
if tolerance <= 0.0 then tolerance := BezierTolerance;
|
|
setLength(Result, 1);
|
|
Result[0] := pts[0];
|
|
for i := 0 to (highI div 2) -1 do
|
|
begin
|
|
if PointsEqual(pts[i*2], pts[i*2+1]) and
|
|
PointsEqual(pts[i*2+1], pts[i*2+2]) then
|
|
begin
|
|
AppendPoint(Result, pts[i*2]);
|
|
AppendPoint(Result, pts[i*2 +2]);
|
|
end else
|
|
begin
|
|
p := FlattenQBezier(pts[i*2], pts[i*2+1], pts[i*2+2], tolerance);
|
|
AppendPath(Result, Copy(p, 1, Length(p) -1));
|
|
end;
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function FlattenQBezier(const pt1, pt2, pt3: TPointD;
|
|
tolerance: double = 0.0): TPathD;
|
|
var
|
|
resultCnt, resultLen: integer;
|
|
|
|
procedure AddPoint(const pt: TPointD);
|
|
begin
|
|
if resultCnt = resultLen then
|
|
begin
|
|
inc(resultLen, BuffSize);
|
|
setLength(result, resultLen);
|
|
end;
|
|
result[resultCnt] := pt;
|
|
inc(resultCnt);
|
|
end;
|
|
|
|
procedure DoCurve(const p1, p2, p3: TPointD);
|
|
var
|
|
p12, p23, p123: TPointD;
|
|
begin
|
|
if (abs(p1.x + p3.x - 2 * p2.x) +
|
|
abs(p1.y + p3.y - 2 * p2.y) < tolerance) then
|
|
begin
|
|
AddPoint(p3);
|
|
end else
|
|
begin
|
|
P12.X := (P1.X + P2.X) * 0.5;
|
|
P12.Y := (P1.Y + P2.Y) * 0.5;
|
|
P23.X := (P2.X + P3.X) * 0.5;
|
|
P23.Y := (P2.Y + P3.Y) * 0.5;
|
|
P123.X := (P12.X + P23.X) * 0.5;
|
|
P123.Y := (P12.Y + P23.Y) * 0.5;
|
|
DoCurve(p1, p12, p123);
|
|
DoCurve(p123, p23, p3);
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
resultLen := 0; resultCnt := 0;
|
|
if tolerance <= 0.0 then tolerance := BezierTolerance;
|
|
AddPoint(pt1);
|
|
if ((pt1.X = pt2.X) and (pt1.Y = pt2.Y)) or
|
|
((pt2.X = pt3.X) and (pt2.Y = pt3.Y)) then
|
|
begin
|
|
AddPoint(pt3)
|
|
end else
|
|
DoCurve(pt1, pt2, pt3);
|
|
SetLength(result, resultCnt);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function GetPointInCubicBezier(const a,b,c,d: TPointD; t: double): TPointD;
|
|
var
|
|
omt: double;
|
|
begin
|
|
if t > 1 then t := 1
|
|
else if t < 0 then t := 0;
|
|
omt := 1 - t;
|
|
Result.X := a.X*omt*omt*omt +b.X*3*omt*omt*t +c.X*3*omt*t*t +d.X*t*t*t;
|
|
Result.Y := a.Y*omt*omt*omt +b.Y*3*omt*omt*t +c.Y*3*omt*t*t +d.Y*t*t*t;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function FlattenCBezier(const firstPt: TPointD; const pts: TPathD;
|
|
tolerance: double = 0.0): TPathD; overload;
|
|
begin
|
|
Result := FlattenCBezier(PrePendPoint(firstPt, pts), tolerance);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function FlattenCBezier(const pts: TPathD; tolerance: double = 0.0): TPathD;
|
|
var
|
|
i, len: integer;
|
|
p: TPathD;
|
|
begin
|
|
Result := nil;
|
|
len := Length(pts) -1;
|
|
if len < 0 then Exit;
|
|
if (len < 3) or (len mod 3 <> 0) then
|
|
raise Exception.Create(rsInvalidCBezier);
|
|
if tolerance <= 0.0 then tolerance := BezierTolerance;
|
|
setLength(Result, 1);
|
|
Result[0] := pts[0];
|
|
for i := 0 to (len div 3) -1 do
|
|
begin
|
|
if PointsEqual(pts[i*3], pts[i*3+1]) and
|
|
PointsEqual(pts[i*3+2], pts[i*3+3]) then
|
|
begin
|
|
AppendPoint(Result, pts[i*3]);
|
|
AppendPoint(Result, pts[i*3 +3]);
|
|
end else
|
|
begin
|
|
p := FlattenCBezier(pts[i*3], pts[i*3+1],
|
|
pts[i*3+2], pts[i*3+3], tolerance);
|
|
AppendPath(Result, Copy(p, 1, Length(p) -1));
|
|
end;
|
|
end;
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function FlattenCBezier(const pt1, pt2, pt3, pt4: TPointD;
|
|
tolerance: double = 0.0): TPathD;
|
|
var
|
|
resultCnt, resultLen: integer;
|
|
|
|
procedure AddPoint(const pt: TPointD);
|
|
begin
|
|
if resultCnt = resultLen then
|
|
begin
|
|
inc(resultLen, BuffSize);
|
|
setLength(result, resultLen);
|
|
end;
|
|
result[resultCnt] := pt;
|
|
inc(resultCnt);
|
|
end;
|
|
|
|
procedure DoCurve(const p1, p2, p3, p4: TPointD);
|
|
var
|
|
p12, p23, p34, p123, p234, p1234: TPointD;
|
|
begin
|
|
if ((abs(p1.x +p3.x - 2*p2.x) < tolerance) and
|
|
(abs(p2.x +p4.x - 2*p3.x) < tolerance)) and
|
|
((abs(p1.y +p3.y - 2*p2.y) < tolerance) and
|
|
(abs(p2.y +p4.y - 2*p3.y) < tolerance)) then
|
|
begin
|
|
AddPoint(p4);
|
|
end else
|
|
begin
|
|
p12.X := (p1.X + p2.X) / 2;
|
|
p12.Y := (p1.Y + p2.Y) / 2;
|
|
p23.X := (p2.X + p3.X) / 2;
|
|
p23.Y := (p2.Y + p3.Y) / 2;
|
|
p34.X := (p3.X + p4.X) / 2;
|
|
p34.Y := (p3.Y + p4.Y) / 2;
|
|
p123.X := (p12.X + p23.X) / 2;
|
|
p123.Y := (p12.Y + p23.Y) / 2;
|
|
p234.X := (p23.X + p34.X) / 2;
|
|
p234.Y := (p23.Y + p34.Y) / 2;
|
|
p1234.X := (p123.X + p234.X) / 2;
|
|
p1234.Y := (p123.Y + p234.Y) / 2;
|
|
DoCurve(p1, p12, p123, p1234);
|
|
DoCurve(p1234, p234, p34, p4);
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
result := nil;
|
|
resultLen := 0; resultCnt := 0;
|
|
if tolerance <= 0.0 then tolerance := BezierTolerance;
|
|
AddPoint(pt1);
|
|
if ValueAlmostZero(pt1.X - pt2.X) and ValueAlmostZero(pt1.Y - pt2.Y) and
|
|
ValueAlmostZero(pt3.X - pt4.X) and ValueAlmostZero(pt3.Y - pt4.Y) then
|
|
begin
|
|
AddPoint(pt4)
|
|
end else
|
|
DoCurve(pt1, pt2, pt3, pt4);
|
|
SetLength(result,resultCnt);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function ReflectPoint(const pt, pivot: TPointD): TPointD;
|
|
begin
|
|
Result.X := pivot.X + (pivot.X - pt.X);
|
|
Result.Y := pivot.Y + (pivot.Y - pt.Y);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function FlattenCSpline(const priorCtrlPt, startPt: TPointD;
|
|
const pts: TPathD; tolerance: double = 0.0): TPathD;
|
|
var
|
|
p: TPathD;
|
|
len: integer;
|
|
begin
|
|
len := Length(pts);
|
|
SetLength(p, len + 2);
|
|
p[0] := startPt;
|
|
p[1] := ReflectPoint(priorCtrlPt, startPt);
|
|
if len > 0 then
|
|
Move(pts[0], p[2], len * SizeOf(TPointD));
|
|
Result := FlattenCSpline(p, tolerance);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function FlattenCSpline(const pts: TPathD; tolerance: double = 0.0): TPathD;
|
|
var
|
|
resultCnt, resultLen: integer;
|
|
|
|
procedure AddPoint(const pt: TPointD);
|
|
begin
|
|
if resultCnt = resultLen then
|
|
begin
|
|
inc(resultLen, BuffSize);
|
|
setLength(result, resultLen);
|
|
end;
|
|
result[resultCnt] := pt;
|
|
inc(resultCnt);
|
|
end;
|
|
|
|
procedure DoCurve(const p1, p2, p3, p4: TPointD);
|
|
var
|
|
p12, p23, p34, p123, p234, p1234: TPointD;
|
|
begin
|
|
if (abs(p1.x + p3.x - 2*p2.x) + abs(p2.x + p4.x - 2*p3.x) +
|
|
abs(p1.y + p3.y - 2*p2.y) + abs(p2.y + p4.y - 2*p3.y)) < tolerance then
|
|
begin
|
|
if resultCnt = length(result) then
|
|
setLength(result, length(result) +BuffSize);
|
|
result[resultCnt] := p4;
|
|
inc(resultCnt);
|
|
end else
|
|
begin
|
|
p12.X := (p1.X + p2.X) / 2;
|
|
p12.Y := (p1.Y + p2.Y) / 2;
|
|
p23.X := (p2.X + p3.X) / 2;
|
|
p23.Y := (p2.Y + p3.Y) / 2;
|
|
p34.X := (p3.X + p4.X) / 2;
|
|
p34.Y := (p3.Y + p4.Y) / 2;
|
|
p123.X := (p12.X + p23.X) / 2;
|
|
p123.Y := (p12.Y + p23.Y) / 2;
|
|
p234.X := (p23.X + p34.X) / 2;
|
|
p234.Y := (p23.Y + p34.Y) / 2;
|
|
p1234.X := (p123.X + p234.X) / 2;
|
|
p1234.Y := (p123.Y + p234.Y) / 2;
|
|
DoCurve(p1, p12, p123, p1234);
|
|
DoCurve(p1234, p234, p34, p4);
|
|
end;
|
|
end;
|
|
|
|
var
|
|
i, len: integer;
|
|
p: PPointD;
|
|
pt1,pt2,pt3,pt4: TPointD;
|
|
begin
|
|
result := nil;
|
|
len := Length(pts); resultLen := 0; resultCnt := 0;
|
|
if (len < 4) then Exit;
|
|
if tolerance <= 0.0 then tolerance := BezierTolerance;
|
|
//ignore incomplete trailing control points
|
|
if Odd(len) then dec(len);
|
|
p := @pts[0];
|
|
AddPoint(p^);
|
|
pt1 := p^; inc(p);
|
|
pt2 := p^; inc(p);
|
|
for i := 0 to (len shr 1) - 2 do
|
|
begin
|
|
pt3 := p^; inc(p);
|
|
pt4 := p^; inc(p);
|
|
DoCurve(pt1, pt2, pt3, pt4);
|
|
pt1 := pt4;
|
|
pt2 := ReflectPoint(pt3, pt1);
|
|
end;
|
|
SetLength(result,resultCnt);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function FlattenQSpline(const priorCtrlPt, startPt: TPointD;
|
|
const pts: TPathD; tolerance: double = 0.0): TPathD;
|
|
var
|
|
p: TPathD;
|
|
len: integer;
|
|
begin
|
|
len := Length(pts);
|
|
SetLength(p, len + 2);
|
|
p[0] := startPt;
|
|
p[1] := ReflectPoint(priorCtrlPt, startPt);
|
|
if len > 0 then
|
|
Move(pts[0], p[2], len * SizeOf(TPointD));
|
|
Result := FlattenQSpline(p, tolerance);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function FlattenQSpline(const pts: TPathD; tolerance: double = 0.0): TPathD;
|
|
var
|
|
resultCnt, resultLen: integer;
|
|
|
|
procedure AddPoint(const pt: TPointD);
|
|
begin
|
|
if resultCnt = resultLen then
|
|
begin
|
|
inc(resultLen, BuffSize);
|
|
setLength(result, resultLen);
|
|
end;
|
|
result[resultCnt] := pt;
|
|
inc(resultCnt);
|
|
end;
|
|
|
|
procedure DoCurve(const p1, p2, p3: TPointD);
|
|
var
|
|
p12, p23, p123: TPointD;
|
|
begin
|
|
if (abs(p1.x + p3.x - 2 * p2.x) +
|
|
abs(p1.y + p3.y - 2 * p2.y) < tolerance) then
|
|
begin
|
|
AddPoint(p3);
|
|
end else
|
|
begin
|
|
P12.X := (P1.X + P2.X) * 0.5;
|
|
P12.Y := (P1.Y + P2.Y) * 0.5;
|
|
P23.X := (P2.X + P3.X) * 0.5;
|
|
P23.Y := (P2.Y + P3.Y) * 0.5;
|
|
P123.X := (P12.X + P23.X) * 0.5;
|
|
P123.Y := (P12.Y + P23.Y) * 0.5;
|
|
DoCurve(p1, p12, p123);
|
|
DoCurve(p123, p23, p3);
|
|
end;
|
|
end;
|
|
|
|
var
|
|
i, len: integer;
|
|
p: PPointD;
|
|
pt1, pt2, pt3: TPointD;
|
|
begin
|
|
result := nil;
|
|
len := Length(pts);
|
|
if (len < 3) then Exit;
|
|
resultLen := 0;
|
|
resultCnt := 0;
|
|
if tolerance <= 0.0 then tolerance := BezierTolerance;
|
|
p := @pts[0];
|
|
AddPoint(p^);
|
|
pt1 := p^; inc(p);
|
|
pt2 := p^; inc(p);
|
|
for i := 0 to len - 3 do
|
|
begin
|
|
pt3 := p^; inc(p);
|
|
DoCurve(pt1, pt2, pt3);
|
|
pt1 := pt3;
|
|
pt2 := ReflectPoint(pt2, pt1);
|
|
end;
|
|
SetLength(result,resultCnt);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
function MakePath(const pts: array of double): TPathD;
|
|
var
|
|
i, j, len: Integer;
|
|
x,y: double;
|
|
begin
|
|
Result := nil;
|
|
len := length(pts) div 2;
|
|
if len = 0 then Exit;
|
|
setlength(Result, len);
|
|
Result[0].X := pts[0];
|
|
Result[0].Y := pts[1];
|
|
j := 0;
|
|
for i := 1 to len -1 do
|
|
begin
|
|
x := pts[i*2];
|
|
y := pts[i*2 +1];
|
|
inc(j);
|
|
Result[j].X := x;
|
|
Result[j].Y := y;
|
|
end;
|
|
setlength(Result, j+1);
|
|
end;
|
|
//------------------------------------------------------------------------------
|
|
|
|
end.
|