| 1 | {$IFDEF INCLUDE_INTERFACE}
|
|---|
| 2 | {$UNDEF INCLUDE_INTERFACE}
|
|---|
| 3 |
|
|---|
| 4 | type
|
|---|
| 5 | { TCubicBezierCurve }
|
|---|
| 6 | {* Definition of a Bézier curve of order 3. It has two control points ''c1'' and ''c2''. Those are not reached by the curve }
|
|---|
| 7 | TCubicBezierCurve = object
|
|---|
| 8 | private
|
|---|
| 9 | function SimpleComputePoints(AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
|
|---|
| 10 | public
|
|---|
| 11 | {** Starting point (reached) }
|
|---|
| 12 | p1: TPointF;
|
|---|
| 13 | {** First control point (not reached by the curve) }
|
|---|
| 14 | c1: TPointF;
|
|---|
| 15 | {** Second control point (not reached by the curve) }
|
|---|
| 16 | c2: TPointF;
|
|---|
| 17 | {** Ending point (reached) }
|
|---|
| 18 | p2: TPointF;
|
|---|
| 19 | {** Computes the point at time ''t'', varying from 0 to 1 }
|
|---|
| 20 | function ComputePointAt(t: single): TPointF;
|
|---|
| 21 | {** Split the curve in two such that ''ALeft.p2'' = ''ARight.p1'' }
|
|---|
| 22 | procedure Split(out ALeft, ARight: TCubicBezierCurve);
|
|---|
| 23 | {** Compute an approximation of the length of the curve. ''AAcceptedDeviation'' indicates the
|
|---|
| 24 | maximum orthogonal distance that is ignored and approximated by a straight line. }
|
|---|
| 25 | function ComputeLength(AAcceptedDeviation: single = 0.1): single;
|
|---|
| 26 | {** Computes a polygonal approximation of the curve. ''AAcceptedDeviation'' indicates the
|
|---|
| 27 | maximum orthogonal distance that is ignored and approximated by a straight line.
|
|---|
| 28 | ''AIncludeFirstPoint'' indicates if the first point must be included in the array }
|
|---|
| 29 | function ToPoints(AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
|
|---|
| 30 | procedure CopyToPath(ADest: IBGRAPath);
|
|---|
| 31 | function GetBounds: TRectF;
|
|---|
| 32 | end;
|
|---|
| 33 |
|
|---|
| 34 | {** Creates a structure for a cubic Bézier curve }
|
|---|
| 35 | function BezierCurve(origin, control1, control2, destination: TPointF) : TCubicBezierCurve; overload;
|
|---|
| 36 |
|
|---|
| 37 | type
|
|---|
| 38 | { TQuadraticBezierCurve }
|
|---|
| 39 | {* Definition of a Bézier curve of order 2. It has one control point }
|
|---|
| 40 | TQuadraticBezierCurve = object
|
|---|
| 41 | private
|
|---|
| 42 | function SimpleComputePoints(AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
|
|---|
| 43 | function ComputeExtremumPositionOutsideSegment: single;
|
|---|
| 44 | public
|
|---|
| 45 | {** Starting point (reached) }
|
|---|
| 46 | p1: TPointF;
|
|---|
| 47 | {** Control point (not reached by the curve) }
|
|---|
| 48 | c: TPointF;
|
|---|
| 49 | {** Ending point (reached) }
|
|---|
| 50 | p2: TPointF;
|
|---|
| 51 | {** Computes the point at time ''t'', varying from 0 to 1 }
|
|---|
| 52 | function ComputePointAt(t: single): TPointF;
|
|---|
| 53 | {** Split the curve in two such that ''ALeft.p2'' = ''ARight.p1'' }
|
|---|
| 54 | procedure Split(out ALeft, ARight: TQuadraticBezierCurve);
|
|---|
| 55 | {** Compute the '''exact''' length of the curve }
|
|---|
| 56 | function ComputeLength: single;
|
|---|
| 57 | {** Computes a polygonal approximation of the curve. ''AAcceptedDeviation'' indicates the
|
|---|
| 58 | maximum orthogonal distance that is ignored and approximated by a straight line.
|
|---|
| 59 | ''AIncludeFirstPoint'' indicates if the first point must be included in the array }
|
|---|
| 60 | function ToPoints(AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
|
|---|
| 61 | procedure CopyToPath(ADest: IBGRAPath);
|
|---|
| 62 | function GetBounds: TRectF;
|
|---|
| 63 | end;
|
|---|
| 64 |
|
|---|
| 65 | {** Creates a structure for a quadratic Bézier curve }
|
|---|
| 66 | function BezierCurve(origin, control, destination: TPointF) : TQuadraticBezierCurve; overload;
|
|---|
| 67 | {** Creates a structure for a quadratic Bézier curve without curvature }
|
|---|
| 68 | function BezierCurve(origin, destination: TPointF) : TQuadraticBezierCurve; overload;
|
|---|
| 69 |
|
|---|
| 70 | type
|
|---|
| 71 | { A quasi-standard rational quadratic Bezier curve is defined by three points and a number:
|
|---|
| 72 | p1 = starting point
|
|---|
| 73 | c = control point
|
|---|
| 74 | p2 = ending point
|
|---|
| 75 | weight = weight for the control point
|
|---|
| 76 |
|
|---|
| 77 | The curve is defined with the function (t in [0;1]):
|
|---|
| 78 | f: t -> ((1-t)^2*p1 + 2*t*(1-t)*weight*c + t^2*p2) / (1-t)^2 + 2*t*(1-t)*weight + t^2)
|
|---|
| 79 |
|
|---|
| 80 | The curve is an arc of:
|
|---|
| 81 | - ellipse when weight in ]-1;1[
|
|---|
| 82 | - parabola when weight = 1 (classical quadratic Bezier curve)
|
|---|
| 83 | - hyperbola when weight > 1
|
|---|
| 84 |
|
|---|
| 85 | A negative weight give the complementary curve for its positive counterpart.
|
|---|
| 86 | So when weight <= -1 the curve is discontinuous:
|
|---|
| 87 | - infinite branches of parabola when weight = -1
|
|---|
| 88 | - infinite branches of hyperbola and symetric hyperbola when weight < -1
|
|---|
| 89 |
|
|---|
| 90 | To transform a rational quadratic Bezier curve with an affin transformation, you
|
|---|
| 91 | only have to transform the three points and leave the weight as it is. }
|
|---|
| 92 |
|
|---|
| 93 | ArrayOfSingle = array of single;
|
|---|
| 94 |
|
|---|
| 95 | { TRationalQuadraticBezierCurve }
|
|---|
| 96 | {* Definition of a quasi-standard rational Bézier curve of order 2. It has one weighted control point }
|
|---|
| 97 | TRationalQuadraticBezierCurve = object
|
|---|
| 98 | //** Starting, control and ending points
|
|---|
| 99 | p1, c, p2 : TPointF;
|
|---|
| 100 | //** Weight of control point
|
|---|
| 101 | weight : single;
|
|---|
| 102 | private
|
|---|
| 103 | function GetIsInfinite: boolean;
|
|---|
| 104 | function InternalComputePoints(AInfiniteBounds: TRectF; AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
|
|---|
| 105 | function GetBoundingPositions(AIncludeFirstAndLast: boolean; ASorted: boolean): ArrayOfSingle;
|
|---|
| 106 | public
|
|---|
| 107 | function ComputePointAt(t: single): TPointF;
|
|---|
| 108 | function ComputeLength(AAcceptedDeviation: single = 0.1): single;
|
|---|
| 109 | function ToPoints(AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF; overload;
|
|---|
| 110 | function ToPoints(AInfiniteBounds: TRectF; AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF; overload;
|
|---|
| 111 | function GetBounds: TRectF;
|
|---|
| 112 | procedure Split(out ALeft, ARight: TRationalQuadraticBezierCurve);
|
|---|
| 113 | property IsInfinite: boolean read GetIsInfinite;
|
|---|
| 114 | end;
|
|---|
| 115 |
|
|---|
| 116 | function BezierCurve(origin, control, destination: TPointF; Aweight:single) : TRationalQuadraticBezierCurve; overload;
|
|---|
| 117 |
|
|---|
| 118 | type
|
|---|
| 119 | TEasyBezierCurveMode= (cmAuto, cmCurve, cmAngle);
|
|---|
| 120 | TEasyBezierPointTransformFunc = function(APoint: PPointF; AData: Pointer): TPointF of object;
|
|---|
| 121 |
|
|---|
| 122 | { TEasyBezierCurve }
|
|---|
| 123 |
|
|---|
| 124 | TEasyBezierCurve = object
|
|---|
| 125 | private
|
|---|
| 126 | function GetCurveMode(AIndex: integer): TEasyBezierCurveMode;
|
|---|
| 127 | function GetCurveStartPoint: TPointF;
|
|---|
| 128 | function GetPoint(AIndex: integer): TPointF;
|
|---|
| 129 | function GetPointCount: integer;
|
|---|
| 130 | procedure SetClosed(AValue: boolean);
|
|---|
| 131 | procedure SetCurveMode(AIndex: integer; AValue: TEasyBezierCurveMode);
|
|---|
| 132 | procedure SetMinimumDotProduct(AValue: single);
|
|---|
| 133 | procedure SetPoint(AIndex: integer; AValue: TPointF);
|
|---|
| 134 | protected
|
|---|
| 135 | FCurves: array of record
|
|---|
| 136 | isCurvedToNext,isCurvedToPrevious: boolean;
|
|---|
| 137 | Center,ControlPoint,NextCenter: TPointF;
|
|---|
| 138 | end;
|
|---|
| 139 | FInvalidated: boolean;
|
|---|
| 140 | FPoints: array of record
|
|---|
| 141 | Coord: TPointF;
|
|---|
| 142 | CurveMode: TEasyBezierCurveMode;
|
|---|
| 143 | end;
|
|---|
| 144 | FMinimumDotProduct: single;
|
|---|
| 145 | FClosed: boolean;
|
|---|
| 146 | function MaybeCurve(start1, end1, start2, end2: integer): boolean;
|
|---|
| 147 | procedure ComputeQuadraticCurves;
|
|---|
| 148 | function PointTransformNone(APoint: PPointF; {%H-}AData: Pointer): TPointF;
|
|---|
| 149 | function PointTransformOffset(APoint: PPointF; AData: Pointer): TPointF;
|
|---|
| 150 | public
|
|---|
| 151 | procedure Init;
|
|---|
| 152 | procedure Clear;
|
|---|
| 153 | procedure SetPoints(APoints: array of TPointF; ACurveMode: TEasyBezierCurveMode); overload;
|
|---|
| 154 | procedure SetPoints(APoints: array of TPointF; ACurveMode: array of TEasyBezierCurveMode); overload;
|
|---|
| 155 | procedure CopyToPath(ADest: IBGRAPath); overload;
|
|---|
| 156 | procedure CopyToPath(ADest: IBGRAPath; AOffset: TPointF); overload;
|
|---|
| 157 | procedure CopyToPath(ADest: IBGRAPath; ATransformFunc: TEasyBezierPointTransformFunc; ATransformData: Pointer); overload;
|
|---|
| 158 | property Point[AIndex: integer]: TPointF read GetPoint write SetPoint;
|
|---|
| 159 | property CurveMode[AIndex: integer]: TEasyBezierCurveMode read GetCurveMode write SetCurveMode;
|
|---|
| 160 | property PointCount: integer read GetPointCount;
|
|---|
| 161 | property MinimumDotProduct: single read FMinimumDotProduct write SetMinimumDotProduct;
|
|---|
| 162 | property Closed: boolean read FClosed write SetClosed;
|
|---|
| 163 | property CurveStartPoint: TPointF read GetCurveStartPoint;
|
|---|
| 164 | function ToPoints: ArrayOfTPointF;
|
|---|
| 165 | function ComputeLength: single;
|
|---|
| 166 | end;
|
|---|
| 167 |
|
|---|
| 168 | const
|
|---|
| 169 | EasyBezierDefaultMinimumDotProduct = 0.707;
|
|---|
| 170 |
|
|---|
| 171 | function EasyBezierCurve(APoints: array of TPointF; AClosed: boolean; ACurveMode: TEasyBezierCurveMode;
|
|---|
| 172 | AMinimumDotProduct: single = EasyBezierDefaultMinimumDotProduct): TEasyBezierCurve; overload;
|
|---|
| 173 |
|
|---|
| 174 | function EasyBezierCurve(APoints: array of TPointF; AClosed: boolean; ACurveMode: array of TEasyBezierCurveMode;
|
|---|
| 175 | AMinimumDotProduct: single = EasyBezierDefaultMinimumDotProduct): TEasyBezierCurve; overload;
|
|---|
| 176 |
|
|---|
| 177 | {$ENDIF}
|
|---|
| 178 |
|
|---|
| 179 | {$IFDEF INCLUDE_IMPLEMENTATION}
|
|---|
| 180 | {$UNDEF INCLUDE_IMPLEMENTATION}
|
|---|
| 181 | //-------------- Bézier curves definitions ----------------
|
|---|
| 182 | // See : http://en.wikipedia.org/wiki/B%C3%A9zier_curve
|
|---|
| 183 |
|
|---|
| 184 | // Define a Bézier curve with two control points.
|
|---|
| 185 | function BezierCurve(origin, control1, control2, destination: TPointF): TCubicBezierCurve;
|
|---|
| 186 | begin
|
|---|
| 187 | result.p1 := origin;
|
|---|
| 188 | result.c1 := control1;
|
|---|
| 189 | result.c2 := control2;
|
|---|
| 190 | result.p2 := destination;
|
|---|
| 191 | end;
|
|---|
| 192 |
|
|---|
| 193 | // Define a Bézier curve with one control point.
|
|---|
| 194 | function BezierCurve(origin, control, destination: TPointF
|
|---|
| 195 | ): TQuadraticBezierCurve;
|
|---|
| 196 | begin
|
|---|
| 197 | result.p1 := origin;
|
|---|
| 198 | result.c := control;
|
|---|
| 199 | result.p2 := destination;
|
|---|
| 200 | end;
|
|---|
| 201 |
|
|---|
| 202 | //straight line
|
|---|
| 203 | function BezierCurve(origin, destination: TPointF): TQuadraticBezierCurve;
|
|---|
| 204 | begin
|
|---|
| 205 | result.p1 := origin;
|
|---|
| 206 | result.c := (origin+destination)*0.5;
|
|---|
| 207 | result.p2 := destination;
|
|---|
| 208 | end;
|
|---|
| 209 |
|
|---|
| 210 | // rational Bezier curve
|
|---|
| 211 | function BezierCurve(origin, control, destination: TPointF; Aweight:single) : TRationalQuadraticBezierCurve;
|
|---|
| 212 | begin
|
|---|
| 213 | result.p1 := origin;
|
|---|
| 214 | result.c := control;
|
|---|
| 215 | result.p2 := destination;
|
|---|
| 216 | result.weight := Aweight;
|
|---|
| 217 | end;
|
|---|
| 218 |
|
|---|
| 219 | function ComputeBezierCurvePrecision(pt1, pt2, pt3, pt4: TPointF; AAcceptedDeviation: single = 0.1): integer;
|
|---|
| 220 | var
|
|---|
| 221 | len: single;
|
|---|
| 222 | begin
|
|---|
| 223 | len := sqr(pt1.x - pt2.x) + sqr(pt1.y - pt2.y);
|
|---|
| 224 | len := max(len, sqr(pt3.x - pt2.x) + sqr(pt3.y - pt2.y));
|
|---|
| 225 | len := max(len, sqr(pt3.x - pt4.x) + sqr(pt3.y - pt4.y));
|
|---|
| 226 | Result := round(sqrt(sqrt(len)/ AAcceptedDeviation) * 1);
|
|---|
| 227 | if Result<=0 then Result:=1;
|
|---|
| 228 | end;
|
|---|
| 229 |
|
|---|
| 230 | { TCubicBezierCurve }
|
|---|
| 231 |
|
|---|
| 232 | function TCubicBezierCurve.SimpleComputePoints(AAcceptedDeviation: single;
|
|---|
| 233 | AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
|
|---|
| 234 | var
|
|---|
| 235 | t,step: single;
|
|---|
| 236 | i,nb: Integer;
|
|---|
| 237 | a,b,c: TpointF;
|
|---|
| 238 | begin
|
|---|
| 239 | nb := ComputeBezierCurvePrecision(p1,c1,c2,p2, AAcceptedDeviation/2);
|
|---|
| 240 | if nb <= 1 then nb := 2;
|
|---|
| 241 | a:=p2-p1+3*(c1-c2);
|
|---|
| 242 | b:=3*(p1+c2)-6*c1;
|
|---|
| 243 | c:=3*(c1-p1);
|
|---|
| 244 | if AIncludeFirstPoint then
|
|---|
| 245 | begin
|
|---|
| 246 | setlength(result,nb);
|
|---|
| 247 | result[0] := p1;
|
|---|
| 248 | result[nb-1] := p2;
|
|---|
| 249 | step := 1/(nb-1);
|
|---|
| 250 | t := 0;
|
|---|
| 251 | for i := 1 to nb-2 do
|
|---|
| 252 | begin
|
|---|
| 253 | t += step;
|
|---|
| 254 | result[i] := p1+t*(c+t*(b+t*a))
|
|---|
| 255 | end;
|
|---|
| 256 | end else
|
|---|
| 257 | begin
|
|---|
| 258 | setlength(result,nb-1);
|
|---|
| 259 | result[nb-2] := p2;
|
|---|
| 260 | step := 1/(nb-1);
|
|---|
| 261 | t := 0;
|
|---|
| 262 | for i := 0 to nb-3 do
|
|---|
| 263 | begin
|
|---|
| 264 | t += step;
|
|---|
| 265 | result[i] := p1+t*(c+t*(b+t*a))
|
|---|
| 266 | end;
|
|---|
| 267 | end;
|
|---|
| 268 | end;
|
|---|
| 269 |
|
|---|
| 270 | function TCubicBezierCurve.ComputePointAt(t: single): TPointF;
|
|---|
| 271 | var
|
|---|
| 272 | f1,f2,f3,f4: single;
|
|---|
| 273 | begin
|
|---|
| 274 | f1 := (1-t);
|
|---|
| 275 | f2 := f1*f1;
|
|---|
| 276 | f1 *= f2;
|
|---|
| 277 | f2 *= t*3;
|
|---|
| 278 | f4 := t*t;
|
|---|
| 279 | f3 := f4*(1-t)*3;
|
|---|
| 280 | f4 *= t;
|
|---|
| 281 |
|
|---|
| 282 | result.x := f1*p1.x + f2*c1.x +
|
|---|
| 283 | f3*c2.x + f4*p2.x;
|
|---|
| 284 | result.y := f1*p1.y + f2*c1.y +
|
|---|
| 285 | f3*c2.y + f4*p2.y;
|
|---|
| 286 | end;
|
|---|
| 287 |
|
|---|
| 288 | procedure TCubicBezierCurve.Split(out ALeft, ARight: TCubicBezierCurve);
|
|---|
| 289 | var midc: TPointF;
|
|---|
| 290 | begin
|
|---|
| 291 | ALeft.p1 := p1;
|
|---|
| 292 | ALeft.c1 := 0.5*(p1+c1);
|
|---|
| 293 | ARight.p2 := p2;
|
|---|
| 294 | ARight.c2 := 0.5*(p2+c2);
|
|---|
| 295 | midc := 0.5*(c1+c2);
|
|---|
| 296 | ALeft.c2 := 0.5*(ALeft.c1+midc);
|
|---|
| 297 | ARight.c1 := 0.5*(ARight.c2+midc);
|
|---|
| 298 | ALeft.p2 := 0.5*(ALeft.c2+ARight.c1);
|
|---|
| 299 | ARight.p1 := ALeft.p2;
|
|---|
| 300 | end;
|
|---|
| 301 |
|
|---|
| 302 | function TCubicBezierCurve.ComputeLength(AAcceptedDeviation: single): single;
|
|---|
| 303 | var
|
|---|
| 304 | t,step: single;
|
|---|
| 305 | i,nb: Integer;
|
|---|
| 306 | curCoord,nextCoord: TPointF;
|
|---|
| 307 | begin
|
|---|
| 308 | nb := ComputeBezierCurvePrecision(p1,c1,c2,p2, AAcceptedDeviation);
|
|---|
| 309 | if nb <= 1 then nb := 2;
|
|---|
| 310 | result := 0;
|
|---|
| 311 | curCoord := p1;
|
|---|
| 312 | step := 1/(nb-1);
|
|---|
| 313 | t := 0;
|
|---|
| 314 | for i := 1 to nb-2 do
|
|---|
| 315 | begin
|
|---|
| 316 | t += step;
|
|---|
| 317 | nextCoord := ComputePointAt(t);
|
|---|
| 318 | result += VectLen(nextCoord-curCoord);
|
|---|
| 319 | curCoord := nextCoord;
|
|---|
| 320 | end;
|
|---|
| 321 | result += VectLen(p2-curCoord);
|
|---|
| 322 | end;
|
|---|
| 323 |
|
|---|
| 324 | function TCubicBezierCurve.ToPoints(AAcceptedDeviation: single;
|
|---|
| 325 | AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
|
|---|
| 326 | begin
|
|---|
| 327 | result := SimpleComputePoints(AAcceptedDeviation, AIncludeFirstPoint);
|
|---|
| 328 | end;
|
|---|
| 329 |
|
|---|
| 330 | procedure TCubicBezierCurve.CopyToPath(ADest: IBGRAPath);
|
|---|
| 331 | begin
|
|---|
| 332 | ADest.lineTo(p1);
|
|---|
| 333 | ADest.bezierCurveTo(c1,c2,p2);
|
|---|
| 334 | end;
|
|---|
| 335 |
|
|---|
| 336 | {//The following function computes by splitting the curve. It is slower than the simple function.
|
|---|
| 337 | function TCubicBezierCurve.ToPoints(AAcceptedDeviation: single;
|
|---|
| 338 | ARelativeDeviation: boolean): ArrayOfTPointF;
|
|---|
| 339 | function ToPointsRec(const ACurve: TCubicBezierCurve): ArrayOfTPointF;
|
|---|
| 340 | var simpleLen2: single;
|
|---|
| 341 | v: TPointF;
|
|---|
| 342 | left,right: TCubicBezierCurve;
|
|---|
| 343 | subLeft,subRight: ArrayOfTPointF;
|
|---|
| 344 | maxDev,dev1,dev2: single;
|
|---|
| 345 | subLeftLen: integer;
|
|---|
| 346 |
|
|---|
| 347 | procedure ComputeExtremum;
|
|---|
| 348 | begin
|
|---|
| 349 | raise Exception.Create('Not implemented');
|
|---|
| 350 | result := nil;
|
|---|
| 351 | end;
|
|---|
| 352 |
|
|---|
| 353 | begin
|
|---|
| 354 | v := ACurve.p2-ACurve.p1;
|
|---|
| 355 | simpleLen2 := v*v;
|
|---|
| 356 | if simpleLen2 = 0 then
|
|---|
| 357 | begin
|
|---|
| 358 | if (ACurve.c1.x = ACurve.p1.x) and (ACurve.c1.y = ACurve.p1.y) and
|
|---|
| 359 | (ACurve.c2.x = ACurve.p2.x) and (ACurve.c2.y = ACurve.p2.y) then
|
|---|
| 360 | begin
|
|---|
| 361 | result := nil;
|
|---|
| 362 | exit;
|
|---|
| 363 | end;
|
|---|
| 364 | ACurve.Split(left,right);
|
|---|
| 365 | end else
|
|---|
| 366 | begin
|
|---|
| 367 | ACurve.Split(left,right);
|
|---|
| 368 | if not ARelativeDeviation then simpleLen2:= sqrt(simpleLen2);
|
|---|
| 369 | maxDev := AAcceptedDeviation*simpleLen2;
|
|---|
| 370 | if abs(PointF(v.y,-v.x) * (left.p2-ACurve.p1)) <= maxDev then
|
|---|
| 371 | begin
|
|---|
| 372 | dev1 := PointF(v.y,-v.x) * (ACurve.c1-ACurve.p1);
|
|---|
| 373 | dev2 := PointF(v.y,-v.x) * (ACurve.c2-ACurve.p2);
|
|---|
| 374 | if not ((Sign(dev1)<>Sign(dev2)) and ((abs(dev1) > maxDev) or (abs(dev2) > maxDev))) then
|
|---|
| 375 | begin
|
|---|
| 376 | result := nil;
|
|---|
| 377 | if ((ACurve.c1-ACurve.p1)*v < -maxDev) or
|
|---|
| 378 | ((ACurve.c1-ACurve.p2)*v > maxDev) or
|
|---|
| 379 | ((ACurve.c2-ACurve.p1)*v < -maxDev) or
|
|---|
| 380 | ((ACurve.c2-ACurve.p2)*v > maxDev) then
|
|---|
| 381 | ComputeExtremum;
|
|---|
| 382 | exit;
|
|---|
| 383 | end;
|
|---|
| 384 | end;
|
|---|
| 385 | end;
|
|---|
| 386 | subRight := ToPointsRec(right);
|
|---|
| 387 | subLeft := ToPointsRec(left);
|
|---|
| 388 | subLeftLen := length(subLeft);
|
|---|
| 389 |
|
|---|
| 390 | //avoid leaving a gap in memory
|
|---|
| 391 | result := subLeft;
|
|---|
| 392 | subLeft := nil;
|
|---|
| 393 | setlength(result, subLeftLen+1+length(subRight));
|
|---|
| 394 | result[subLeftLen] := left.p2;
|
|---|
| 395 | move(subRight[0], result[subLeftLen+1], length(subRight)*sizeof(TPointF));
|
|---|
| 396 | end;
|
|---|
| 397 |
|
|---|
| 398 | var
|
|---|
| 399 | subLen: integer;
|
|---|
| 400 |
|
|---|
| 401 | begin
|
|---|
| 402 | if (c1.x = p1.x) and (c1.y = p1.y) and
|
|---|
| 403 | (c1.x = c2.x) and (c1.y = c2.y) and
|
|---|
| 404 | (c1.x = p2.x) and (c1.y = p2.y) then
|
|---|
| 405 | begin
|
|---|
| 406 | setlength(result,1);
|
|---|
| 407 | result[0] := c1;
|
|---|
| 408 | exit;
|
|---|
| 409 | end else
|
|---|
| 410 | begin
|
|---|
| 411 | result := ToPointsRec(self);
|
|---|
| 412 | subLen := length(result);
|
|---|
| 413 | setlength(result, length(result)+2);
|
|---|
| 414 | move(result[0], result[1], subLen*sizeof(TPointF));
|
|---|
| 415 | result[0] := p1;
|
|---|
| 416 | result[high(result)] := p2;
|
|---|
| 417 | end;
|
|---|
| 418 | end;}
|
|---|
| 419 |
|
|---|
| 420 | function TCubicBezierCurve.GetBounds: TRectF;
|
|---|
| 421 | const precision = 1e-5;
|
|---|
| 422 |
|
|---|
| 423 | procedure Include(pt: TPointF);
|
|---|
| 424 | begin
|
|---|
| 425 | if pt.x < result.Left then result.Left := pt.x
|
|---|
| 426 | else if pt.x > result.Right then result.Right := pt.x;
|
|---|
| 427 | if pt.y < result.Top then result.Top := pt.y
|
|---|
| 428 | else if pt.y > result.Bottom then result.Bottom := pt.y;
|
|---|
| 429 | end;
|
|---|
| 430 |
|
|---|
| 431 | procedure IncludeT(t: single);
|
|---|
| 432 | begin
|
|---|
| 433 | if (t > 0) and (t < 1) then
|
|---|
| 434 | Include(ComputePointAt(t));
|
|---|
| 435 | end;
|
|---|
| 436 |
|
|---|
| 437 | procedure IncludeABC(a,b,c: single);
|
|---|
| 438 | var b2ac, sqrtb2ac: single;
|
|---|
| 439 | begin
|
|---|
| 440 | if abs(a) < precision then
|
|---|
| 441 | begin
|
|---|
| 442 | if abs(b) < precision then exit;
|
|---|
| 443 | IncludeT(-c/b);
|
|---|
| 444 | end else
|
|---|
| 445 | begin
|
|---|
| 446 | b2ac := sqr(b) - 4 * a * c;
|
|---|
| 447 | if b2ac >= 0 then
|
|---|
| 448 | begin
|
|---|
| 449 | sqrtb2ac := sqrt(b2ac);
|
|---|
| 450 | IncludeT((-b + sqrtb2ac) / (2 * a));
|
|---|
| 451 | IncludeT((-b - sqrtb2ac) / (2 * a));
|
|---|
| 452 | end;
|
|---|
| 453 | end;
|
|---|
| 454 | end;
|
|---|
| 455 |
|
|---|
| 456 | var
|
|---|
| 457 | va, vb, vc: TPointF;
|
|---|
| 458 |
|
|---|
| 459 | begin
|
|---|
| 460 | result.TopLeft := p1;
|
|---|
| 461 | result.BottomRight := p1;
|
|---|
| 462 | Include(p2);
|
|---|
| 463 |
|
|---|
| 464 | vb := 6 * p1 - 12 * c1 + 6 * c2;
|
|---|
| 465 | va := -3 * p1 + 9 * c1 - 9 * c2 + 3 * p2;
|
|---|
| 466 | vc := 3 * c1 - 3 * p1;
|
|---|
| 467 |
|
|---|
| 468 | IncludeABC(va.x,vb.x,vc.x);
|
|---|
| 469 | IncludeABC(va.y,vb.y,vc.y);
|
|---|
| 470 | end;
|
|---|
| 471 |
|
|---|
| 472 | { TQuadraticBezierCurve }
|
|---|
| 473 |
|
|---|
| 474 | function TQuadraticBezierCurve.SimpleComputePoints(AAcceptedDeviation: single;
|
|---|
| 475 | AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
|
|---|
| 476 | var
|
|---|
| 477 | t,step: single;
|
|---|
| 478 | i,nb: Integer;
|
|---|
| 479 | pA,pB : TpointF;
|
|---|
| 480 | begin
|
|---|
| 481 | nb := ComputeBezierCurvePrecision(p1,c,c,p2, AAcceptedDeviation);
|
|---|
| 482 | if nb <= 1 then nb := 2;
|
|---|
| 483 | pA := p2+p1-2*c; pB := 2*(c-p1);
|
|---|
| 484 | if AIncludeFirstPoint then
|
|---|
| 485 | begin
|
|---|
| 486 | setlength(result,nb);
|
|---|
| 487 | result[0] := p1;
|
|---|
| 488 | result[nb-1] := p2;
|
|---|
| 489 | step := 1/(nb-1);
|
|---|
| 490 | t := 0;
|
|---|
| 491 | for i := 1 to nb-2 do
|
|---|
| 492 | begin
|
|---|
| 493 | t += step;
|
|---|
| 494 | result[i] := p1+t*(pB+t*pA);
|
|---|
| 495 | end;
|
|---|
| 496 | end else
|
|---|
| 497 | begin
|
|---|
| 498 | setlength(result,nb-1);
|
|---|
| 499 | result[nb-2] := p2;
|
|---|
| 500 | step := 1/(nb-1);
|
|---|
| 501 | t := 0;
|
|---|
| 502 | for i := 0 to nb-3 do
|
|---|
| 503 | begin
|
|---|
| 504 | t += step;
|
|---|
| 505 | result[i] := p1+t*(pB+t*pA);
|
|---|
| 506 | end;
|
|---|
| 507 | end;
|
|---|
| 508 | end;
|
|---|
| 509 |
|
|---|
| 510 | function TQuadraticBezierCurve.ComputeExtremumPositionOutsideSegment: single;
|
|---|
| 511 | var a,b: single;
|
|---|
| 512 | v: TPointF;
|
|---|
| 513 | begin
|
|---|
| 514 | v := self.p2-self.p1;
|
|---|
| 515 | a := (self.p1-2*self.c+self.p2)*v;
|
|---|
| 516 | if a = 0 then //no solution
|
|---|
| 517 | begin
|
|---|
| 518 | result := -1;
|
|---|
| 519 | exit;
|
|---|
| 520 | end;
|
|---|
| 521 | b := (self.c-self.p1)*v;
|
|---|
| 522 | result := -b/a;
|
|---|
| 523 | end;
|
|---|
| 524 |
|
|---|
| 525 | function TQuadraticBezierCurve.ComputePointAt(t: single): TPointF;
|
|---|
| 526 | var
|
|---|
| 527 | rev_t,f2,t2: single;
|
|---|
| 528 | begin
|
|---|
| 529 | rev_t := (1-t);
|
|---|
| 530 | f2 := rev_t*t*2;
|
|---|
| 531 | rev_t *= rev_t;
|
|---|
| 532 | t2 := t*t;
|
|---|
| 533 | result.x := rev_t*p1.x + f2*c.x + t2*p2.x;
|
|---|
| 534 | result.y := rev_t*p1.y + f2*c.y + t2*p2.y;
|
|---|
| 535 | end;
|
|---|
| 536 |
|
|---|
| 537 | procedure TQuadraticBezierCurve.Split(out ALeft, ARight: TQuadraticBezierCurve);
|
|---|
| 538 | begin
|
|---|
| 539 | ALeft.p1 := p1;
|
|---|
| 540 | ALeft.c := 0.5*(p1+c);
|
|---|
| 541 | ARight.p2 := p2;
|
|---|
| 542 | ARight.c := 0.5*(p2+c);
|
|---|
| 543 | ALeft.p2 := 0.5*(ALeft.c+ARight.c);
|
|---|
| 544 | ARight.p1 := ALeft.p2;
|
|---|
| 545 | end;
|
|---|
| 546 |
|
|---|
| 547 | function TQuadraticBezierCurve.ComputeLength: single;
|
|---|
| 548 | var a,b: TPointF;
|
|---|
| 549 | A_,AB_,B_,Sabc,A_2,A_32,B_2,BA,
|
|---|
| 550 | divisor: single;
|
|---|
| 551 | extremumPos: single;
|
|---|
| 552 | extremum: TPointF;
|
|---|
| 553 | begin
|
|---|
| 554 | a := p1 - 2*c + p2;
|
|---|
| 555 | b := 2*(c - p1);
|
|---|
| 556 | A_ := 4*(a*a);
|
|---|
| 557 | B_ := b*b;
|
|---|
| 558 | if (A_ = 0) or (B_ = 0) then
|
|---|
| 559 | begin
|
|---|
| 560 | result := VectLen(p2-p1);
|
|---|
| 561 | exit;
|
|---|
| 562 | end;
|
|---|
| 563 | AB_ := 4*(a*b);
|
|---|
| 564 |
|
|---|
| 565 | A_2 := sqrt(A_);
|
|---|
| 566 | B_2 := 2*sqrt(B_);
|
|---|
| 567 | BA := AB_/A_2;
|
|---|
| 568 | divisor := BA+B_2;
|
|---|
| 569 | if divisor <= 0 then
|
|---|
| 570 | begin
|
|---|
| 571 | extremumPos:= ComputeExtremumPositionOutsideSegment;
|
|---|
| 572 | if (extremumPos <= 0) or (extremumPos >= 1) then
|
|---|
| 573 | result := VectLen(p2-p1)
|
|---|
| 574 | else
|
|---|
| 575 | begin
|
|---|
| 576 | extremum := ComputePointAt(extremumPos);
|
|---|
| 577 | result := VectLen(extremum-p1)+VectLen(p2-extremum);
|
|---|
| 578 | end;
|
|---|
| 579 | exit;
|
|---|
| 580 | end;
|
|---|
| 581 |
|
|---|
| 582 | Sabc := 2*sqrt(A_+AB_+B_);
|
|---|
| 583 | A_32 := 2*A_*A_2;
|
|---|
| 584 | result := ( A_32*Sabc +
|
|---|
| 585 | A_2*AB_*(Sabc-B_2) +
|
|---|
| 586 | (4*B_*A_-AB_*AB_)*ln( (2*A_2+BA+Sabc)/divisor )
|
|---|
| 587 | )/(4*A_32);
|
|---|
| 588 | end;
|
|---|
| 589 |
|
|---|
| 590 | function TQuadraticBezierCurve.ToPoints(AAcceptedDeviation: single;
|
|---|
| 591 | AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
|
|---|
| 592 | begin
|
|---|
| 593 | result := SimpleComputePoints(AAcceptedDeviation, AIncludeFirstPoint);
|
|---|
| 594 | end;
|
|---|
| 595 |
|
|---|
| 596 | procedure TQuadraticBezierCurve.CopyToPath(ADest: IBGRAPath);
|
|---|
| 597 | begin
|
|---|
| 598 | ADest.lineTo(p1);
|
|---|
| 599 | ADest.quadraticCurveTo(c,p2);
|
|---|
| 600 | end;
|
|---|
| 601 |
|
|---|
| 602 | function TQuadraticBezierCurve.GetBounds: TRectF;
|
|---|
| 603 | const precision = 1e-5;
|
|---|
| 604 |
|
|---|
| 605 | procedure Include(pt: TPointF);
|
|---|
| 606 | begin
|
|---|
| 607 | if pt.x < result.Left then result.Left := pt.x
|
|---|
| 608 | else if pt.x > result.Right then result.Right := pt.x;
|
|---|
| 609 | if pt.y < result.Top then result.Top := pt.y
|
|---|
| 610 | else if pt.y > result.Bottom then result.Bottom := pt.y;
|
|---|
| 611 | end;
|
|---|
| 612 |
|
|---|
| 613 | procedure IncludeT(t: single);
|
|---|
| 614 | begin
|
|---|
| 615 | if (t > 0) and (t < 1) then
|
|---|
| 616 | Include(ComputePointAt(t));
|
|---|
| 617 | end;
|
|---|
| 618 |
|
|---|
| 619 | procedure IncludeABC(a,b,c: single);
|
|---|
| 620 | var denom: single;
|
|---|
| 621 | begin
|
|---|
| 622 | denom := a-2*b+c;
|
|---|
| 623 | if abs(denom) < precision then exit;
|
|---|
| 624 | IncludeT((a-b)/denom);
|
|---|
| 625 | end;
|
|---|
| 626 |
|
|---|
| 627 | begin
|
|---|
| 628 | result.TopLeft := p1;
|
|---|
| 629 | result.BottomRight := p1;
|
|---|
| 630 | Include(p2);
|
|---|
| 631 |
|
|---|
| 632 | IncludeABC(p1.x,c.x,p2.x);
|
|---|
| 633 | IncludeABC(p1.y,c.y,p2.y);
|
|---|
| 634 | end;
|
|---|
| 635 |
|
|---|
| 636 | {//The following function computes by splitting the curve. It is slower than the simple function
|
|---|
| 637 | function TQuadraticBezierCurve.ToPoints(AAcceptedDeviation: single; ARelativeDeviation: boolean): ArrayOfTPointF;
|
|---|
| 638 |
|
|---|
| 639 | function ToPointsRec(const ACurve: TQuadraticBezierCurve): ArrayOfTPointF;
|
|---|
| 640 | var simpleLen2: single;
|
|---|
| 641 | v: TPointF;
|
|---|
| 642 | left,right: TQuadraticBezierCurve;
|
|---|
| 643 | subLeft,subRight: ArrayOfTPointF;
|
|---|
| 644 | subLeftLen: Integer;
|
|---|
| 645 |
|
|---|
| 646 | procedure ComputeExtremum;
|
|---|
| 647 | var
|
|---|
| 648 | t: single;
|
|---|
| 649 | begin
|
|---|
| 650 | t := ACurve.ComputeExtremumPositionOutsideSegment;
|
|---|
| 651 | if (t <= 0) or (t >= 1) then
|
|---|
| 652 | result := nil
|
|---|
| 653 | else
|
|---|
| 654 | begin
|
|---|
| 655 | setlength(result,1);
|
|---|
| 656 | result[0] := ACurve.ComputePointAt(t);
|
|---|
| 657 | end;
|
|---|
| 658 | end;
|
|---|
| 659 |
|
|---|
| 660 | begin
|
|---|
| 661 | v := ACurve.p2-ACurve.p1;
|
|---|
| 662 | simpleLen2 := v*v;
|
|---|
| 663 | if simpleLen2 = 0 then
|
|---|
| 664 | begin
|
|---|
| 665 | if (ACurve.c.x = ACurve.p1.x) and (ACurve.c.y = ACurve.p1.y) then
|
|---|
| 666 | begin
|
|---|
| 667 | result := nil;
|
|---|
| 668 | exit;
|
|---|
| 669 | end;
|
|---|
| 670 | ACurve.Split(left,right);
|
|---|
| 671 | end else
|
|---|
| 672 | begin
|
|---|
| 673 | ACurve.Split(left,right);
|
|---|
| 674 | if not ARelativeDeviation then simpleLen2:= sqrt(simpleLen2);
|
|---|
| 675 | if abs(PointF(v.y,-v.x) * (left.p2-ACurve.p1))
|
|---|
| 676 | <= AAcceptedDeviation*simpleLen2 then
|
|---|
| 677 | begin
|
|---|
| 678 | result := nil;
|
|---|
| 679 | if ((ACurve.c-ACurve.p1)*v < -AAcceptedDeviation*simpleLen2) or
|
|---|
| 680 | ((ACurve.c-ACurve.p2)*v > AAcceptedDeviation*simpleLen2) then
|
|---|
| 681 | ComputeExtremum;
|
|---|
| 682 | exit;
|
|---|
| 683 | end;
|
|---|
| 684 | end;
|
|---|
| 685 | subRight := ToPointsRec(right);
|
|---|
| 686 | subLeft := ToPointsRec(left);
|
|---|
| 687 | subLeftLen := length(subLeft);
|
|---|
| 688 |
|
|---|
| 689 | //avoid leaving a gap in memory
|
|---|
| 690 | result := subLeft;
|
|---|
| 691 | subLeft := nil;
|
|---|
| 692 | setlength(result, subLeftLen+1+length(subRight));
|
|---|
| 693 | result[subLeftLen] := left.p2;
|
|---|
| 694 | move(subRight[0], result[subLeftLen+1], length(subRight)*sizeof(TPointF));
|
|---|
| 695 | end;
|
|---|
| 696 |
|
|---|
| 697 | var
|
|---|
| 698 | subLen: integer;
|
|---|
| 699 |
|
|---|
| 700 | begin
|
|---|
| 701 | if (c.x = p1.x) and (c.y = p1.y) and
|
|---|
| 702 | (c.x = p2.x) and (c.y = p2.y) then
|
|---|
| 703 | begin
|
|---|
| 704 | setlength(result,1);
|
|---|
| 705 | result[0] := c;
|
|---|
| 706 | exit;
|
|---|
| 707 | end else
|
|---|
| 708 | begin
|
|---|
| 709 | result := ToPointsRec(self);
|
|---|
| 710 | subLen := length(result);
|
|---|
| 711 | setlength(result, length(result)+2);
|
|---|
| 712 | move(result[0], result[1], subLen*sizeof(TPointF));
|
|---|
| 713 | result[0] := p1;
|
|---|
| 714 | result[high(result)] := p2;
|
|---|
| 715 | end;
|
|---|
| 716 | end;}
|
|---|
| 717 |
|
|---|
| 718 | { TRationalQuadraticBezierCurve }
|
|---|
| 719 |
|
|---|
| 720 | function TRationalQuadraticBezierCurve.GetIsInfinite: boolean;
|
|---|
| 721 | begin
|
|---|
| 722 | result:= (weight <= -1);
|
|---|
| 723 | end;
|
|---|
| 724 |
|
|---|
| 725 | function TRationalQuadraticBezierCurve.InternalComputePoints(AInfiniteBounds: TRectF; AAcceptedDeviation: single;
|
|---|
| 726 | AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
|
|---|
| 727 | var
|
|---|
| 728 | pA,pB : TpointF;
|
|---|
| 729 | a1,b1: single;
|
|---|
| 730 |
|
|---|
| 731 | function InternalComputeAt(t: single): TPointF;
|
|---|
| 732 | var
|
|---|
| 733 | den: single;
|
|---|
| 734 | begin
|
|---|
| 735 | den := (1+t*(b1+t*a1));
|
|---|
| 736 | if den <> 0 then
|
|---|
| 737 | result := (p1+t*(pB+t*pA))*(1/den)
|
|---|
| 738 | else
|
|---|
| 739 | result := EmptyPointF
|
|---|
| 740 | end;
|
|---|
| 741 |
|
|---|
| 742 | procedure ComputeFactors;
|
|---|
| 743 | var
|
|---|
| 744 | c2 : TpointF;
|
|---|
| 745 | c1: single;
|
|---|
| 746 | begin
|
|---|
| 747 | c1 := 2*weight; c2 := c1*c;
|
|---|
| 748 | pA := p2+p1-c2; pB := -2*p1+c2;
|
|---|
| 749 | a1 := 2-c1; b1 := -a1;
|
|---|
| 750 | end;
|
|---|
| 751 |
|
|---|
| 752 | function ComputeContinuous(t1,t2: single; AIncludeFirstPoint: boolean): ArrayOfTPointF;
|
|---|
| 753 | var
|
|---|
| 754 | pointCount: integer;
|
|---|
| 755 |
|
|---|
| 756 | procedure AddPoint(APoint: TPointF);
|
|---|
| 757 | begin
|
|---|
| 758 | if isEmptyPointF(APoint) then exit;
|
|---|
| 759 | if pointCount >= length(result) then
|
|---|
| 760 | setlength(result, pointCount*2+4);
|
|---|
| 761 | result[pointCount] := APoint;
|
|---|
| 762 | inc(pointCount);
|
|---|
| 763 | end;
|
|---|
| 764 |
|
|---|
| 765 | procedure ComputeRec(left: single; constref leftPoint: TPointF; right: single; constref rightPoint: TPointF);
|
|---|
| 766 | var
|
|---|
| 767 | middlePoint, u: TPointF;
|
|---|
| 768 | middle, lenU, deviation: Single;
|
|---|
| 769 | begin
|
|---|
| 770 | if rightPoint<>leftPoint then
|
|---|
| 771 | begin
|
|---|
| 772 | middle := (left+right)*0.5;
|
|---|
| 773 | middlePoint := InternalComputeAt(middle);
|
|---|
| 774 | u := rightPoint-leftPoint;
|
|---|
| 775 | lenU := VectLen(u);
|
|---|
| 776 | if lenU>0 then u *= (1/lenU);
|
|---|
| 777 | deviation := abs((middlePoint-leftPoint)*PointF(u.y,-u.x));
|
|---|
| 778 | if deviation > AAcceptedDeviation then
|
|---|
| 779 | begin
|
|---|
| 780 | ComputeRec(left, leftPoint, middle, middlePoint);
|
|---|
| 781 | AddPoint(middlePoint);
|
|---|
| 782 | ComputeRec(middle, middlePoint, right, rightPoint);
|
|---|
| 783 | end else
|
|---|
| 784 | if deviation > AAcceptedDeviation*0.6 then
|
|---|
| 785 | AddPoint(middlePoint);
|
|---|
| 786 | end;
|
|---|
| 787 | end;
|
|---|
| 788 |
|
|---|
| 789 | var
|
|---|
| 790 | startPoint, endPoint: TPointF;
|
|---|
| 791 | begin
|
|---|
| 792 | pointCount := 0;
|
|---|
| 793 | result:= nil;
|
|---|
| 794 | startPoint := InternalComputeAt(t1);
|
|---|
| 795 | endPoint := InternalComputeAt(t2);
|
|---|
| 796 | if AIncludeFirstPoint then AddPoint(startPoint);
|
|---|
| 797 | if endPoint <> startPoint then
|
|---|
| 798 | begin
|
|---|
| 799 | ComputeRec(t1,startPoint,t2,endPoint);
|
|---|
| 800 | AddPoint(endPoint);
|
|---|
| 801 | end;
|
|---|
| 802 | setlength(result,PointCount);
|
|---|
| 803 | end;
|
|---|
| 804 |
|
|---|
| 805 | var
|
|---|
| 806 | tSplitA, tSplitB, tSplit1, tSplit2, delta: single;
|
|---|
| 807 | leftPart,middlePart,rightPart: array of TPointF;
|
|---|
| 808 | tList: ArrayOfSingle;
|
|---|
| 809 | parts: array of ArrayOfTPointF;
|
|---|
| 810 | i: Integer;
|
|---|
| 811 |
|
|---|
| 812 | function PointWithinInifiniteBounds(APoint: TPointF): boolean;
|
|---|
| 813 | begin
|
|---|
| 814 | result := not isEmptyPointF(APoint) and
|
|---|
| 815 | (APoint.x > AInfiniteBounds.Left) and (APoint.x < AInfiniteBounds.Right) and
|
|---|
| 816 | (APoint.y > AInfiniteBounds.Top) and (APoint.y < AInfiniteBounds.Bottom);
|
|---|
| 817 | end;
|
|---|
| 818 |
|
|---|
| 819 | begin
|
|---|
| 820 | if weight = 0 then exit(PointsF([p1,p2]));
|
|---|
| 821 | ComputeFactors;
|
|---|
| 822 |
|
|---|
| 823 | if weight > -1 then
|
|---|
| 824 | begin
|
|---|
| 825 | tList := GetBoundingPositions(true,true);
|
|---|
| 826 | setlength(parts, length(tList)-1);
|
|---|
| 827 | for i := 0 to high(parts) do
|
|---|
| 828 | parts[i] := ComputeContinuous(tList[i],tList[i+1], AIncludeFirstPoint and (i=0));
|
|---|
| 829 | result := ConcatPointsF(parts);
|
|---|
| 830 | end
|
|---|
| 831 | else
|
|---|
| 832 | if weight = -1 then
|
|---|
| 833 | begin
|
|---|
| 834 | tSplit1 := 0.5;
|
|---|
| 835 | tSplitA := 0;
|
|---|
| 836 | while PointWithinInifiniteBounds(InternalComputeAt(tSplitA)) do tSplitA := (tSplitA+tSplit1)*0.5;
|
|---|
| 837 | tSplitB := 1;
|
|---|
| 838 | while PointWithinInifiniteBounds(InternalComputeAt(tSplitB)) do tSplitB := (tSplitB+tSplit1)*0.5;
|
|---|
| 839 |
|
|---|
| 840 | tList := GetBoundingPositions(true,true);
|
|---|
| 841 | setlength(parts, length(tList)-1);
|
|---|
| 842 | for i := 0 to high(parts) do
|
|---|
| 843 | begin
|
|---|
| 844 | if (tList[i] > tSplitA) and (tList[i+1] <= tSplitB) then parts[i] := nil
|
|---|
| 845 | else
|
|---|
| 846 | if (tList[i] <= tSplitA) and (tList[i+1] >= tSplitA) then
|
|---|
| 847 | begin
|
|---|
| 848 | parts[i] := ComputeContinuous(tList[i],tSplitA, AIncludeFirstPoint or (i>0));
|
|---|
| 849 | setlength(parts[i], length(parts[i])+1);
|
|---|
| 850 | parts[i][high(parts[i])] := EmptyPointF;
|
|---|
| 851 |
|
|---|
| 852 | if tList[i+1] > tSplitB then
|
|---|
| 853 | parts[i] := ConcatPointsF([parts[i], ComputeContinuous(tSplitB,tList[i+1], true)])
|
|---|
| 854 | else
|
|---|
| 855 | tList[i+1] := tSplitB;
|
|---|
| 856 | end
|
|---|
| 857 | else
|
|---|
| 858 | if (tList[i] < tSplitB) and (tList[i+1] >= tSplitB) then
|
|---|
| 859 | parts[i] := ComputeContinuous(tSplitB,tList[i+1], AIncludeFirstPoint or (i>0))
|
|---|
| 860 | else
|
|---|
| 861 | parts[i] := ComputeContinuous(tList[i],tList[i+1], AIncludeFirstPoint or (i>0));
|
|---|
| 862 | end;
|
|---|
| 863 | result := ConcatPointsF(parts);
|
|---|
| 864 | end else
|
|---|
| 865 | begin
|
|---|
| 866 | delta:= 1 - 2/(1-weight);
|
|---|
| 867 | tSplit1 := (1 - sqrt(delta))/2;
|
|---|
| 868 | tSplit2 := 1-tSplit1;
|
|---|
| 869 |
|
|---|
| 870 | tSplitA := 0;
|
|---|
| 871 | while PointWithinInifiniteBounds(InternalComputeAt(tSplitA)) do tSplitA := (tSplitA+tSplit1)*0.5;
|
|---|
| 872 | leftPart := ComputeContinuous(0, tSplitA, AIncludeFirstPoint);
|
|---|
| 873 |
|
|---|
| 874 | tSplitA := (tSplit1+tSplit2)*0.5;
|
|---|
| 875 | tSplitB := tSplitA;
|
|---|
| 876 | while PointWithinInifiniteBounds(InternalComputeAt(tSplitA)) do tSplitA := (tSplitA+tSplit1)*0.5;
|
|---|
| 877 | while PointWithinInifiniteBounds(InternalComputeAt(tSplitB)) do tSplitB := (tSplitB+tSplit2)*0.5;
|
|---|
| 878 | middlePart := ComputeContinuous(tSplitA, tSplitB, true);
|
|---|
| 879 |
|
|---|
| 880 | tSplitB := 1;
|
|---|
| 881 | while PointWithinInifiniteBounds(InternalComputeAt(tSplitB)) do tSplitB := (tSplitB+tSplit2)*0.5;
|
|---|
| 882 | rightPart:= ComputeContinuous(tSplitB, 1, true);
|
|---|
| 883 | result := ConcatPointsF([leftPart, PointsF([EmptyPointF]), middlePart, PointsF([EmptyPointF]), rightPart]);
|
|---|
| 884 | end;
|
|---|
| 885 | end;
|
|---|
| 886 |
|
|---|
| 887 | function TRationalQuadraticBezierCurve.GetBoundingPositions(
|
|---|
| 888 | AIncludeFirstAndLast: boolean; ASorted: boolean): ArrayOfSingle;
|
|---|
| 889 | const precision = 1e-6;
|
|---|
| 890 | var a,delta,sqrtDelta,den,invDen: single;
|
|---|
| 891 | A_,B_,p2_,c_: TPointF;
|
|---|
| 892 | posCount : integer;
|
|---|
| 893 |
|
|---|
| 894 | procedure Include(t: single);
|
|---|
| 895 | var
|
|---|
| 896 | i: Integer;
|
|---|
| 897 | begin
|
|---|
| 898 | if (t < 0) or (t > 1) then exit;
|
|---|
| 899 | for i := 0 to PosCount-1 do
|
|---|
| 900 | if result[i] = t then exit;
|
|---|
| 901 | result[posCount] := t;
|
|---|
| 902 | inc(posCount);
|
|---|
| 903 | end;
|
|---|
| 904 |
|
|---|
| 905 | procedure SortList;
|
|---|
| 906 | var i,j,k: integer;
|
|---|
| 907 | temp: single;
|
|---|
| 908 | begin
|
|---|
| 909 | for i := 1 to high(result) do
|
|---|
| 910 | begin
|
|---|
| 911 | j := i;
|
|---|
| 912 | while (j > 0) and (result[j-1] > result[i]) do dec(j);
|
|---|
| 913 | if j <> i then
|
|---|
| 914 | begin
|
|---|
| 915 | temp := result[i];
|
|---|
| 916 | for k := i downto j+1 do
|
|---|
| 917 | result[k] := result[k-1];
|
|---|
| 918 | result[j] := temp;
|
|---|
| 919 | end;
|
|---|
| 920 | end;
|
|---|
| 921 | end;
|
|---|
| 922 |
|
|---|
| 923 | begin
|
|---|
| 924 | setlength(result, 6);
|
|---|
| 925 | posCount := 0;
|
|---|
| 926 |
|
|---|
| 927 | if AIncludeFirstAndLast then
|
|---|
| 928 | begin
|
|---|
| 929 | Include(0);
|
|---|
| 930 | Include(1);
|
|---|
| 931 | end;
|
|---|
| 932 |
|
|---|
| 933 | p2_ := p2-p1; c_ := c-p1; //translation with -p1
|
|---|
| 934 | B_ := 2*weight*c_; A_ := p2_-B_;
|
|---|
| 935 | a := 2*(1-weight);
|
|---|
| 936 |
|
|---|
| 937 | //on Ox
|
|---|
| 938 | den := a*p2_.x;
|
|---|
| 939 | if abs(den) >= precision then
|
|---|
| 940 | begin
|
|---|
| 941 | delta := sqr(A_.x)+den*B_.x;
|
|---|
| 942 | if delta >= 0 then
|
|---|
| 943 | begin
|
|---|
| 944 | invDen := 1/den;
|
|---|
| 945 | sqrtDelta := sqrt(delta);
|
|---|
| 946 | Include( (A_.x-sqrtDelta)*invDen );
|
|---|
| 947 | Include( (A_.x+sqrtDelta)*invDen );
|
|---|
| 948 | end;
|
|---|
| 949 | end else //den=0
|
|---|
| 950 | if abs(A_.x) >= precision then
|
|---|
| 951 | Include( -B_.x/A_.x*0.5 );
|
|---|
| 952 |
|
|---|
| 953 | //on Oy
|
|---|
| 954 | den := a*p2_.y;
|
|---|
| 955 | if abs(den) >= precision then
|
|---|
| 956 | begin
|
|---|
| 957 | delta := sqr(A_.y)+den*B_.y;
|
|---|
| 958 | if delta >= 0 then
|
|---|
| 959 | begin
|
|---|
| 960 | invDen := 1/den;
|
|---|
| 961 | sqrtDelta := sqrt(delta);
|
|---|
| 962 | Include( (A_.y-sqrtDelta)*invDen );
|
|---|
| 963 | Include( (A_.y+sqrtDelta)*invDen );
|
|---|
| 964 | end;
|
|---|
| 965 | end else //den=0
|
|---|
| 966 | if abs(A_.y) >= precision then
|
|---|
| 967 | Include( -B_.y/A_.y*0.5 );
|
|---|
| 968 |
|
|---|
| 969 | setlength(result, posCount);
|
|---|
| 970 | if ASorted then SortList;
|
|---|
| 971 | end;
|
|---|
| 972 |
|
|---|
| 973 | function TRationalQuadraticBezierCurve.ComputePointAt(t: single): TPointF;
|
|---|
| 974 | var
|
|---|
| 975 | rev_t,f2,t2,den: single;
|
|---|
| 976 | begin
|
|---|
| 977 | rev_t := (1-t);
|
|---|
| 978 | t2 := t*t;
|
|---|
| 979 | f2 := weight*rev_t*t*2;
|
|---|
| 980 | rev_t *= rev_t;
|
|---|
| 981 | den := rev_t+f2+t2;
|
|---|
| 982 | if den <> 0 then
|
|---|
| 983 | begin
|
|---|
| 984 | result.x := (rev_t*p1.x + f2*c.x + t2*p2.x)/den;
|
|---|
| 985 | result.y := (rev_t*p1.y + f2*c.y + t2*p2.y)/den;
|
|---|
| 986 | end
|
|---|
| 987 | else
|
|---|
| 988 | result := EmptyPointF
|
|---|
| 989 | end;
|
|---|
| 990 |
|
|---|
| 991 | function TRationalQuadraticBezierCurve.ToPoints(AInfiniteBounds: TRectF; AAcceptedDeviation: single;
|
|---|
| 992 | AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
|
|---|
| 993 | begin
|
|---|
| 994 | if weight=1 then
|
|---|
| 995 | result := BezierCurve(p1,c,p2).ToPoints(AAcceptedDeviation, AIncludeFirstPoint)
|
|---|
| 996 | else
|
|---|
| 997 | result := InternalComputePoints(AInfiniteBounds, AAcceptedDeviation, AIncludeFirstPoint)
|
|---|
| 998 | end;
|
|---|
| 999 |
|
|---|
| 1000 | function TRationalQuadraticBezierCurve.GetBounds: TRectF;
|
|---|
| 1001 | var a: single;
|
|---|
| 1002 | A_,B_,p2_,c_: TPointF;
|
|---|
| 1003 | t: single;
|
|---|
| 1004 | tList: array of Single;
|
|---|
| 1005 | i: Integer;
|
|---|
| 1006 |
|
|---|
| 1007 | procedure Include(pt: TPointF);
|
|---|
| 1008 | begin
|
|---|
| 1009 | if pt.x < result.Left then result.Left := pt.x
|
|---|
| 1010 | else if pt.x > result.Right then result.Right := pt.x;
|
|---|
| 1011 | if pt.y < result.Top then result.Top := pt.y
|
|---|
| 1012 | else if pt.y > result.Bottom then result.Bottom := pt.y;
|
|---|
| 1013 | end;
|
|---|
| 1014 |
|
|---|
| 1015 | begin
|
|---|
| 1016 | if weight=1 then exit(BezierCurve(p1,c,p2).GetBounds);
|
|---|
| 1017 | if IsInfinite then exit(EmptyRectF);
|
|---|
| 1018 | tList:= GetBoundingPositions(false,false);
|
|---|
| 1019 |
|
|---|
| 1020 | result.TopLeft := p1;
|
|---|
| 1021 | result.BottomRight := p1;
|
|---|
| 1022 | Include(p2);
|
|---|
| 1023 |
|
|---|
| 1024 | p2_ := p2-p1; c_ := c-p1; //translation with -p1
|
|---|
| 1025 | B_ := 2*weight*c_; A_ := p2_-B_;
|
|---|
| 1026 | a := 2*(1-weight);
|
|---|
| 1027 |
|
|---|
| 1028 | for i := 0 to high(tList) do
|
|---|
| 1029 | begin
|
|---|
| 1030 | t := tList[i];
|
|---|
| 1031 | Include( p1+t*(B_+t*A_)*(1/(1+t*(-a+t*a))) );
|
|---|
| 1032 | end;
|
|---|
| 1033 | end;
|
|---|
| 1034 |
|
|---|
| 1035 | function TRationalQuadraticBezierCurve.ComputeLength(AAcceptedDeviation: single): single;
|
|---|
| 1036 | var i: Integer;
|
|---|
| 1037 | curCoord,nextCoord: TPointF;
|
|---|
| 1038 | pts: ArrayOfTPointF;
|
|---|
| 1039 | begin
|
|---|
| 1040 | if weight = 1 then exit(BezierCurve(p1,c,p2).ComputeLength);
|
|---|
| 1041 | if weight <= -1 then exit(EmptySingle); // no bounds in this case
|
|---|
| 1042 | pts := InternalComputePoints(EmptyRectF, AAcceptedDeviation, true);
|
|---|
| 1043 | curCoord := p1; result:=0;
|
|---|
| 1044 | for i := 1 to high(pts) do
|
|---|
| 1045 | begin
|
|---|
| 1046 | nextCoord := pts[i];
|
|---|
| 1047 | if (nextCoord <> EmptyPointF) and (curCoord <> EmptyPointF) then
|
|---|
| 1048 | result += VectLen(nextCoord-curCoord);
|
|---|
| 1049 | curCoord := nextCoord;
|
|---|
| 1050 | end;
|
|---|
| 1051 | finalize(pts)
|
|---|
| 1052 | end;
|
|---|
| 1053 |
|
|---|
| 1054 | function TRationalQuadraticBezierCurve.ToPoints(AAcceptedDeviation: single;
|
|---|
| 1055 | AIncludeFirstPoint: boolean): ArrayOfTPointF;
|
|---|
| 1056 | begin
|
|---|
| 1057 | result := ToPoints(RectF(-64,-64, 16384, 16384), AAcceptedDeviation, AIncludeFirstPoint);
|
|---|
| 1058 | end;
|
|---|
| 1059 |
|
|---|
| 1060 | procedure TRationalQuadraticBezierCurve.Split(out ALeft, ARight: TRationalQuadraticBezierCurve);
|
|---|
| 1061 | const precision=1E-6;
|
|---|
| 1062 | var M, D, E, H, c1, c2: TPointF;
|
|---|
| 1063 | alpha, sg, w: single;
|
|---|
| 1064 |
|
|---|
| 1065 | function Intersec(): TPointF; //dichotomie
|
|---|
| 1066 | var t, t1, t2: single;
|
|---|
| 1067 | U, V: TPointF;
|
|---|
| 1068 | begin
|
|---|
| 1069 | t1 := 0; t2 := 0.5; U := E-c1;
|
|---|
| 1070 | if VectDet(U,p1-c1)>0 then sg := 1 else sg := -1;
|
|---|
| 1071 | while (t2-t1) > precision do //19 iterations
|
|---|
| 1072 | begin
|
|---|
| 1073 | t := (t1+t2)/2;
|
|---|
| 1074 | V := ComputePointAt(t)-c1;
|
|---|
| 1075 | if VectDet(U,V)*sg>0 then t1 := t else t2 := t;
|
|---|
| 1076 | end;
|
|---|
| 1077 | result := ComputePointAt((t1+t2)/2)
|
|---|
| 1078 | end;
|
|---|
| 1079 |
|
|---|
| 1080 | begin
|
|---|
| 1081 | if IsInfinite then raise exception.Create('Cannot split an infinite curve');
|
|---|
| 1082 |
|
|---|
| 1083 | M := ComputePointAt(0.5);
|
|---|
| 1084 | ALeft.p1 := p1;
|
|---|
| 1085 | ALeft.p2 := M;
|
|---|
| 1086 | ARight.p1 := M;
|
|---|
| 1087 | ARight.p2 := p2;
|
|---|
| 1088 | ALeft.weight := 1;
|
|---|
| 1089 | ARight.weight := 1;
|
|---|
| 1090 | D := 0.5*(p1+p2);
|
|---|
| 1091 | if (weight = 1) or (D = c) then
|
|---|
| 1092 | begin
|
|---|
| 1093 | ALeft.c := 0.5*(p1+c);
|
|---|
| 1094 | ARight.c := 0.5*(p2+c);
|
|---|
| 1095 | exit;
|
|---|
| 1096 | end;
|
|---|
| 1097 | if weight > 0 then
|
|---|
| 1098 | alpha := VectLen(D-M)/VectLen(D-c)
|
|---|
| 1099 | else
|
|---|
| 1100 | alpha := -VectLen(D-M)/VectLen(D-c);
|
|---|
| 1101 | c1 := p1 + alpha*(c-p1);
|
|---|
| 1102 | c2 := p2 + alpha*(c-p2);
|
|---|
| 1103 | ALeft.c := c1;
|
|---|
| 1104 | ARight.c := c2;
|
|---|
| 1105 | E := 0.5*(p1+M);
|
|---|
| 1106 | H := Intersec(); //between [c1;E] and the curve
|
|---|
| 1107 | w := VectLen(E-c1)/VectLen(H-c1)-1; // new weight
|
|---|
| 1108 | ALeft.weight := w;
|
|---|
| 1109 | ARight.weight := w;
|
|---|
| 1110 | end;
|
|---|
| 1111 |
|
|---|
| 1112 | { TEasyBezierCurve }
|
|---|
| 1113 |
|
|---|
| 1114 | function EasyBezierCurve(APoints: array of TPointF; AClosed: boolean;
|
|---|
| 1115 | ACurveMode: TEasyBezierCurveMode; AMinimumDotProduct: single): TEasyBezierCurve;
|
|---|
| 1116 | begin
|
|---|
| 1117 | result.Init;
|
|---|
| 1118 | result.SetPoints(APoints, ACurveMode);
|
|---|
| 1119 | result.Closed:= AClosed;
|
|---|
| 1120 | result.MinimumDotProduct:= AMinimumDotProduct;
|
|---|
| 1121 | end;
|
|---|
| 1122 |
|
|---|
| 1123 | function EasyBezierCurve(APoints: array of TPointF; AClosed: boolean;
|
|---|
| 1124 | ACurveMode: array of TEasyBezierCurveMode;
|
|---|
| 1125 | AMinimumDotProduct: single = EasyBezierDefaultMinimumDotProduct): TEasyBezierCurve;
|
|---|
| 1126 | begin
|
|---|
| 1127 | result.Init;
|
|---|
| 1128 | result.SetPoints(APoints, ACurveMode);
|
|---|
| 1129 | result.Closed:= AClosed;
|
|---|
| 1130 | result.MinimumDotProduct:= AMinimumDotProduct;
|
|---|
| 1131 | end;
|
|---|
| 1132 |
|
|---|
| 1133 | procedure TEasyBezierCurve.CopyToPath(ADest: IBGRAPath; ATransformFunc: TEasyBezierPointTransformFunc; ATransformData: Pointer);
|
|---|
| 1134 | var i: integer;
|
|---|
| 1135 | nextMove: boolean;
|
|---|
| 1136 | pt,startCoord: TPointF;
|
|---|
| 1137 | begin
|
|---|
| 1138 | if PointCount = 0 then exit;
|
|---|
| 1139 | if (FCurves = nil) or FInvalidated then ComputeQuadraticCurves;
|
|---|
| 1140 | nextMove := true;
|
|---|
| 1141 |
|
|---|
| 1142 | for i := 0 to PointCount-1 do
|
|---|
| 1143 | begin
|
|---|
| 1144 | pt := Point[i];
|
|---|
| 1145 | if isEmptyPointF(pt) then
|
|---|
| 1146 | begin
|
|---|
| 1147 | if not nextMove and FClosed then ADest.closePath;
|
|---|
| 1148 | nextMove := true;
|
|---|
| 1149 | end else
|
|---|
| 1150 | begin
|
|---|
| 1151 | with FCurves[i] do
|
|---|
| 1152 | begin
|
|---|
| 1153 | if nextMove then
|
|---|
| 1154 | begin
|
|---|
| 1155 | if not isCurvedToPrevious then
|
|---|
| 1156 | startCoord := pt
|
|---|
| 1157 | else
|
|---|
| 1158 | startCoord := Center;
|
|---|
| 1159 | ADest.moveTo(ATransformFunc(@startCoord,ATransformData));
|
|---|
| 1160 | nextMove := false;
|
|---|
| 1161 | end else
|
|---|
| 1162 | if not isCurvedToPrevious then
|
|---|
| 1163 | ADest.lineTo(ATransformFunc(@pt,ATransformData));
|
|---|
| 1164 |
|
|---|
| 1165 | if isCurvedToNext then
|
|---|
| 1166 | begin
|
|---|
| 1167 | if not isCurvedToPrevious then ADest.lineTo(ATransformFunc(@Center,ATransformData));
|
|---|
| 1168 | ADest.quadraticCurveTo(ATransformFunc(@ControlPoint,ATransformData),ATransformFunc(@NextCenter,ATransformData));
|
|---|
| 1169 | end;
|
|---|
| 1170 | end;
|
|---|
| 1171 | end;
|
|---|
| 1172 | end;
|
|---|
| 1173 | if not nextmove and FClosed then ADest.closePath;
|
|---|
| 1174 | end;
|
|---|
| 1175 |
|
|---|
| 1176 | function TEasyBezierCurve.ToPoints: ArrayOfTPointF;
|
|---|
| 1177 | var p: TBGRACustomPath;
|
|---|
| 1178 | begin
|
|---|
| 1179 | if not Assigned(BGRAPathFactory) then raise exception.Create('BGRAPath unit needed');
|
|---|
| 1180 | p := BGRAPathFactory.Create;
|
|---|
| 1181 | CopyToPath(p);
|
|---|
| 1182 | result := p.getPoints;
|
|---|
| 1183 | p.Free;
|
|---|
| 1184 | end;
|
|---|
| 1185 |
|
|---|
| 1186 | function TEasyBezierCurve.ComputeLength: single;
|
|---|
| 1187 | var p: TBGRACustomPath;
|
|---|
| 1188 | begin
|
|---|
| 1189 | if not Assigned(BGRAPathFactory) then raise exception.Create('BGRAPath unit needed');
|
|---|
| 1190 | p := BGRAPathFactory.Create;
|
|---|
| 1191 | CopyToPath(p);
|
|---|
| 1192 | result := p.getLength;
|
|---|
| 1193 | p.Free;
|
|---|
| 1194 | end;
|
|---|
| 1195 |
|
|---|
| 1196 | procedure TEasyBezierCurve.CopyToPath(ADest: IBGRAPath);
|
|---|
| 1197 | begin
|
|---|
| 1198 | CopyToPath(ADest, @PointTransformNone, nil);
|
|---|
| 1199 | end;
|
|---|
| 1200 |
|
|---|
| 1201 | procedure TEasyBezierCurve.CopyToPath(ADest: IBGRAPath; AOffset: TPointF);
|
|---|
| 1202 | begin
|
|---|
| 1203 | CopyToPath(ADest, @PointTransformOffset, @AOffset);
|
|---|
| 1204 | end;
|
|---|
| 1205 |
|
|---|
| 1206 | procedure TEasyBezierCurve.ComputeQuadraticCurves;
|
|---|
| 1207 | var
|
|---|
| 1208 | i,FirstPointIndex,NextPt,NextPt2: integer;
|
|---|
| 1209 | begin
|
|---|
| 1210 | setlength(FCurves, PointCount);
|
|---|
| 1211 | FirstPointIndex := 0;
|
|---|
| 1212 | for i := 0 to PointCount-1 do
|
|---|
| 1213 | FCurves[i].isCurvedToPrevious := false;
|
|---|
| 1214 | for i := 0 to PointCount-1 do
|
|---|
| 1215 | begin
|
|---|
| 1216 | FCurves[i].isCurvedToNext := false;
|
|---|
| 1217 | FCurves[i].Center := EmptyPointF;
|
|---|
| 1218 | FCurves[i].ControlPoint := EmptyPointF;
|
|---|
| 1219 | FCurves[i].NextCenter := EmptyPointF;
|
|---|
| 1220 |
|
|---|
| 1221 | if IsEmptyPointF(Point[i]) then
|
|---|
| 1222 | begin
|
|---|
| 1223 | FirstPointIndex := i+1;
|
|---|
| 1224 | end else
|
|---|
| 1225 | begin
|
|---|
| 1226 | NextPt := i+1;
|
|---|
| 1227 | if (NextPt = PointCount) or isEmptyPointF(Point[NextPt]) then NextPt := FirstPointIndex;
|
|---|
| 1228 | NextPt2 := NextPt+1;
|
|---|
| 1229 | if (NextPt2 = PointCount) or isEmptyPointF(Point[NextPt2]) then NextPt2 := FirstPointIndex;
|
|---|
| 1230 |
|
|---|
| 1231 | FCurves[i].Center := (Point[i]+Point[NextPt])*0.5;
|
|---|
| 1232 | FCurves[i].NextCenter := (Point[NextPt]+Point[NextPt2])*0.5;
|
|---|
| 1233 | FCurves[i].ControlPoint := Point[NextPt];
|
|---|
| 1234 |
|
|---|
| 1235 | if (i < PointCount-2) or FClosed then
|
|---|
| 1236 | begin
|
|---|
| 1237 | case CurveMode[nextPt] of
|
|---|
| 1238 | cmAuto: FCurves[i].isCurvedToNext:= MaybeCurve(i,NextPt,NextPt,NextPt2);
|
|---|
| 1239 | cmCurve: FCurves[i].isCurvedToNext:= true;
|
|---|
| 1240 | else FCurves[i].isCurvedToNext:= false;
|
|---|
| 1241 | end;
|
|---|
| 1242 | FCurves[NextPt].isCurvedToPrevious := FCurves[i].isCurvedToNext;
|
|---|
| 1243 | end;
|
|---|
| 1244 | end;
|
|---|
| 1245 | end;
|
|---|
| 1246 | FInvalidated:= false;
|
|---|
| 1247 | end;
|
|---|
| 1248 |
|
|---|
| 1249 | function TEasyBezierCurve.PointTransformNone(APoint: PPointF; AData: Pointer): TPointF;
|
|---|
| 1250 | begin
|
|---|
| 1251 | result := APoint^;
|
|---|
| 1252 | end;
|
|---|
| 1253 |
|
|---|
| 1254 | function TEasyBezierCurve.PointTransformOffset(APoint: PPointF; AData: Pointer): TPointF;
|
|---|
| 1255 | begin
|
|---|
| 1256 | result := APoint^ + PPointF(AData)^;
|
|---|
| 1257 | end;
|
|---|
| 1258 |
|
|---|
| 1259 | procedure TEasyBezierCurve.Init;
|
|---|
| 1260 | begin
|
|---|
| 1261 | FClosed := false;
|
|---|
| 1262 | FMinimumDotProduct:= EasyBezierDefaultMinimumDotProduct;
|
|---|
| 1263 | FPoints := nil;
|
|---|
| 1264 | FInvalidated := true;
|
|---|
| 1265 | end;
|
|---|
| 1266 |
|
|---|
| 1267 | procedure TEasyBezierCurve.Clear;
|
|---|
| 1268 | begin
|
|---|
| 1269 | FPoints := nil;
|
|---|
| 1270 | end;
|
|---|
| 1271 |
|
|---|
| 1272 | procedure TEasyBezierCurve.SetPoints(APoints: array of TPointF;
|
|---|
| 1273 | ACurveMode: TEasyBezierCurveMode);
|
|---|
| 1274 | var
|
|---|
| 1275 | i: Integer;
|
|---|
| 1276 | begin
|
|---|
| 1277 | setlength(FPoints, length(APoints));
|
|---|
| 1278 | for i := 0 to high(APoints) do
|
|---|
| 1279 | begin
|
|---|
| 1280 | FPoints[i].Coord := APoints[i];
|
|---|
| 1281 | FPoints[i].CurveMode:= ACurveMode;
|
|---|
| 1282 | end;
|
|---|
| 1283 | FInvalidated:= true;
|
|---|
| 1284 | end;
|
|---|
| 1285 |
|
|---|
| 1286 | procedure TEasyBezierCurve.SetPoints(APoints: array of TPointF;
|
|---|
| 1287 | ACurveMode: array of TEasyBezierCurveMode);
|
|---|
| 1288 | var
|
|---|
| 1289 | i,j: Integer;
|
|---|
| 1290 | begin
|
|---|
| 1291 | setlength(FPoints, length(APoints));
|
|---|
| 1292 | j := 0;
|
|---|
| 1293 | for i := 0 to high(APoints) do
|
|---|
| 1294 | begin
|
|---|
| 1295 | FPoints[i].Coord := APoints[i];
|
|---|
| 1296 | if length(ACurveMode) = 0 then
|
|---|
| 1297 | FPoints[i].CurveMode:= cmAuto
|
|---|
| 1298 | else
|
|---|
| 1299 | begin
|
|---|
| 1300 | FPoints[i].CurveMode:= ACurveMode[j];
|
|---|
| 1301 | inc(j);
|
|---|
| 1302 | if j = length(ACurveMode) then j := 0;
|
|---|
| 1303 | end;
|
|---|
| 1304 | end;
|
|---|
| 1305 | FInvalidated:= true;
|
|---|
| 1306 | end;
|
|---|
| 1307 |
|
|---|
| 1308 | function TEasyBezierCurve.GetCurveMode(AIndex: integer): TEasyBezierCurveMode;
|
|---|
| 1309 | begin
|
|---|
| 1310 | if (AIndex < 0) or (AIndex >= PointCount) then raise exception.Create('Index out of bounds');
|
|---|
| 1311 | result:= FPoints[AIndex].CurveMode;
|
|---|
| 1312 | end;
|
|---|
| 1313 |
|
|---|
| 1314 | function TEasyBezierCurve.GetCurveStartPoint: TPointF;
|
|---|
| 1315 | begin
|
|---|
| 1316 | if (PointCount=0) or isEmptyPointF(Point[0]) then exit(EmptyPointF);
|
|---|
| 1317 | if FInvalidated or (FCurves = nil) then ComputeQuadraticCurves;
|
|---|
| 1318 | if not FCurves[0].isCurvedToPrevious then
|
|---|
| 1319 | result := Point[0]
|
|---|
| 1320 | else
|
|---|
| 1321 | result := FCurves[0].Center;
|
|---|
| 1322 | end;
|
|---|
| 1323 |
|
|---|
| 1324 | function TEasyBezierCurve.GetPoint(AIndex: integer): TPointF;
|
|---|
| 1325 | begin
|
|---|
| 1326 | if (AIndex < 0) or (AIndex >= PointCount) then raise exception.Create('Index out of bounds');
|
|---|
| 1327 | result:= FPoints[AIndex].Coord;
|
|---|
| 1328 | end;
|
|---|
| 1329 |
|
|---|
| 1330 | function TEasyBezierCurve.GetPointCount: integer;
|
|---|
| 1331 | begin
|
|---|
| 1332 | result:= length(FPoints);
|
|---|
| 1333 | end;
|
|---|
| 1334 |
|
|---|
| 1335 | procedure TEasyBezierCurve.SetClosed(AValue: boolean);
|
|---|
| 1336 | begin
|
|---|
| 1337 | if FClosed=AValue then Exit;
|
|---|
| 1338 | FClosed:=AValue;
|
|---|
| 1339 | FInvalidated:= true;
|
|---|
| 1340 | end;
|
|---|
| 1341 |
|
|---|
| 1342 | procedure TEasyBezierCurve.SetCurveMode(AIndex: integer;
|
|---|
| 1343 | AValue: TEasyBezierCurveMode);
|
|---|
| 1344 | begin
|
|---|
| 1345 | if (AIndex < 0) or (AIndex >= PointCount) then raise exception.Create('Index out of bounds');
|
|---|
| 1346 | if FPoints[AIndex].CurveMode = AValue then exit;
|
|---|
| 1347 | FPoints[AIndex].CurveMode := AValue;
|
|---|
| 1348 | FInvalidated:= true;
|
|---|
| 1349 | end;
|
|---|
| 1350 |
|
|---|
| 1351 | procedure TEasyBezierCurve.SetMinimumDotProduct(AValue: single);
|
|---|
| 1352 | begin
|
|---|
| 1353 | if FMinimumDotProduct=AValue then Exit;
|
|---|
| 1354 | FMinimumDotProduct:=AValue;
|
|---|
| 1355 | FInvalidated:= true;
|
|---|
| 1356 | end;
|
|---|
| 1357 |
|
|---|
| 1358 | procedure TEasyBezierCurve.SetPoint(AIndex: integer; AValue: TPointF);
|
|---|
| 1359 | begin
|
|---|
| 1360 | if (AIndex < 0) or (AIndex >= PointCount) then raise exception.Create('Index out of bounds');
|
|---|
| 1361 | if FPoints[AIndex].Coord = AValue then exit;
|
|---|
| 1362 | FPoints[AIndex].Coord := AValue;
|
|---|
| 1363 | FInvalidated:= true;
|
|---|
| 1364 | end;
|
|---|
| 1365 |
|
|---|
| 1366 | function TEasyBezierCurve.MaybeCurve(start1,end1,start2,end2: integer): boolean;
|
|---|
| 1367 | var
|
|---|
| 1368 | u,v: TPointF;
|
|---|
| 1369 | lu,lv: single;
|
|---|
| 1370 | begin
|
|---|
| 1371 | if (start1=-1) or (end1=-1) or (start2=-1) or (end2=-1) then
|
|---|
| 1372 | begin
|
|---|
| 1373 | result := false;
|
|---|
| 1374 | exit;
|
|---|
| 1375 | end;
|
|---|
| 1376 | u := pointF(Point[end1].x - Point[start1].x, Point[end1].y - Point[start1].y);
|
|---|
| 1377 | lu := sqrt(u*u);
|
|---|
| 1378 | if lu <> 0 then u *= 1/lu;
|
|---|
| 1379 | v := pointF(Point[end2].x - Point[start2].x, Point[end2].y - Point[start2].y);
|
|---|
| 1380 | lv := sqrt(v*v);
|
|---|
| 1381 | if lv <> 0 then v *= 1/lv;
|
|---|
| 1382 |
|
|---|
| 1383 | result := u*v > FMinimumDotProduct;
|
|---|
| 1384 | end;
|
|---|
| 1385 |
|
|---|
| 1386 | {$ENDIF}
|
|---|