2023-06-24 18:59:24 +00:00
|
|
|
Unit BSplines;
|
|
|
|
{------------------------------------------------------------------------------}
|
|
|
|
{ }
|
|
|
|
{ This code was written by : M. v. Engeland }
|
|
|
|
{ }
|
|
|
|
{ This code is copyright 2000 by }
|
|
|
|
{ M. v. Engeland }
|
|
|
|
{ }
|
|
|
|
{ }
|
|
|
|
{ This is the followup of the B-splines component posted in 1997. }
|
|
|
|
{ Some bugs were fixed, and some changes were made. The splines now use }
|
|
|
|
{ dynamic allocation of memory when needed when adding points to the spline. }
|
|
|
|
{ If the number of points to be added is known in advance, the the precise }
|
|
|
|
{ amount of memory can be allocated setting the capacity property. }
|
|
|
|
{ }
|
|
|
|
{ The conditions for using this component however are not changed: }
|
|
|
|
{ You may use this component in any way you want to, whether it is for }
|
|
|
|
{ commercial purposes or not, as long as you don't hold me responsible for }
|
|
|
|
{ any disasters it may cause. But I would appreciate it if you would send me }
|
|
|
|
{ an e-mail (martijn@dutw38.wbmt.tudelft.nl) to tell me what you use it for }
|
|
|
|
{ because over the last three years I've learned that it has been used for }
|
|
|
|
{ a wide variaty of interesting applications which it was initially never }
|
|
|
|
{ intended for. Besides, it's nice to know how your offspring's doing. }
|
|
|
|
{ Also all comments and/or remarks are welcome. }
|
|
|
|
{ }
|
|
|
|
{ See the demo program for how to use the component. }
|
|
|
|
{ Special attention is payed on the possibility of interpolating the }
|
|
|
|
{ vertices. As you may or may not know, B-Splines normally do not }
|
|
|
|
{ interpolate the used controlpoints. Thanks to simple matrix calculation }
|
|
|
|
{ however it is possible to interpolate the controlpoints by calculating }
|
|
|
|
{ new vertices in such a way that the spline interpolates the original ones. }
|
|
|
|
{------------------------------------------------------------------------------}
|
|
|
|
|
|
|
|
{$IFDEF FPC}
|
|
|
|
{$MODE ObjFPC}{$H+}
|
|
|
|
{$ENDIF}
|
|
|
|
|
|
|
|
interface
|
|
|
|
|
2023-06-24 22:45:44 +00:00
|
|
|
uses
|
|
|
|
Sysutils, Classes, Graphics; //, Math;
|
2023-06-24 18:59:24 +00:00
|
|
|
|
|
|
|
const MaxFragments = 600; // The maximum of straight line segments allowed for drawing the spline
|
|
|
|
MaxResults = MaxFragments+10; // Max. number of calculated intersections
|
|
|
|
MaxInterpolatedVertices = 250; // The maximum number of vertices that can be interpolated, up to 16000 allowed
|
|
|
|
MaxCalcSteps = 150; // Number of steps for numerical intersection calculating
|
|
|
|
MaxError = 1e-5;// Max error for intersection calculating
|
|
|
|
MaxIterations = 80;
|
|
|
|
VerticesIncrement = 25; // Number of vertices to allocate memory for when the count property exceeds the current capacity
|
|
|
|
|
|
|
|
type TDataType = single;
|
2023-06-24 22:45:44 +00:00
|
|
|
TVertex = record
|
2023-06-24 18:59:24 +00:00
|
|
|
X,Y : TDataType;
|
|
|
|
end;
|
|
|
|
// The following dynamic array is used to store the desired user-specified controlpoints
|
|
|
|
T2DPointList = array[1..1] of TVertex;
|
|
|
|
P2DPointList = ^T2DPointList;
|
|
|
|
|
|
|
|
// The vertexlist is used internally to make the spline interpolate the controlpoints
|
2023-06-24 22:45:44 +00:00
|
|
|
TVertexList = array[0..0] of TVertex;
|
2023-06-24 18:59:24 +00:00
|
|
|
P2DVertexList = ^TVertexList;
|
|
|
|
|
|
|
|
// The knuckle list stores a flag to see whether a point is a knuckle or not
|
|
|
|
TKnuckleList = array[1..1] of Boolean;
|
|
|
|
PKnuckleList = ^TKnuckleList;
|
|
|
|
|
|
|
|
// T2DResults is a record with calculatedvalues at a specific point when for ex. the x-value is known
|
|
|
|
T2DResults = record
|
|
|
|
NumberOfHits : integer;
|
|
|
|
Parameter : array[1..MaxResults] of TDataType;
|
|
|
|
Point : array[1..MaxResults] of TVertex;
|
|
|
|
end;
|
|
|
|
|
|
|
|
// 2D B-spline class:
|
|
|
|
TBSpline = class // 2D B-Spline object
|
|
|
|
private
|
|
|
|
FColor : TColor;
|
|
|
|
FNoPoints : Integer;
|
|
|
|
FCapacity : Integer;
|
|
|
|
FPointList : P2DPointList;
|
|
|
|
FVertexList : P2DVertexList;
|
|
|
|
FKnuckleList : PKnuckleList;
|
|
|
|
FBuild : Boolean;
|
|
|
|
FNoVertices : Integer;
|
|
|
|
FInterpolated : boolean;
|
|
|
|
FMin,FMax : TVertex;
|
|
|
|
FFragments : Integer;
|
|
|
|
FShowVertices : Boolean;
|
|
|
|
procedure FSetBuild(val:boolean);
|
|
|
|
procedure FSetCapacity(Val:Integer); virtual;
|
|
|
|
procedure FSetInterpolated(const value:boolean);
|
|
|
|
procedure FSetFragments(Const value:integer);
|
|
|
|
function FGetNumberOfPoints:integer;
|
|
|
|
function FGetPoint(Index:Integer):TVertex; virtual;
|
|
|
|
procedure FSetPoint(Index:Integer;Value:TVertex); virtual;
|
|
|
|
function FGetVertex(Index:Integer):TVertex; virtual;
|
|
|
|
procedure FSetVertex(Index:Integer;Value:TVertex); virtual;
|
|
|
|
function FGetKnuckle(Index:integer):Boolean; virtual;
|
|
|
|
procedure FSetKnuckle(Index:integer;Value:Boolean); virtual;
|
|
|
|
function FGetNumberOfVertices:integer;
|
|
|
|
procedure FInterpolate; virtual;
|
|
|
|
procedure FFillMatrix; virtual;
|
|
|
|
procedure FPhantomPoints;
|
|
|
|
public
|
|
|
|
procedure AddPoint(Vertex:TVertex); virtual;
|
|
|
|
procedure Extents(Var Min,Max : TVertex); virtual;
|
|
|
|
procedure Clear; virtual;
|
|
|
|
constructor Create;
|
|
|
|
destructor Destroy; override;
|
|
|
|
procedure Draw(Canvas:TCanvas);
|
|
|
|
procedure DeletePoint(Index:Integer);
|
|
|
|
function FirstDerive(Parameter:extended):TVertex; virtual;
|
|
|
|
procedure InsertPoint(Index:Integer;Vertex:TVertex);
|
|
|
|
procedure Invert;// Inverse the controlpoints, eg the last point first and vice versa
|
|
|
|
function SecondDerive(Parameter:extended):TVertex; virtual;// second derivative in a point
|
|
|
|
function KnownX(XValue:TDataType;var Results:T2DResults):Boolean;virtual;
|
|
|
|
function KnownY(YValue:TDataType;var Results:T2DResults):Boolean;virtual;
|
|
|
|
function Value(Parameter:extended):TVertex; virtual;
|
|
|
|
procedure Rebuild; virtual;
|
|
|
|
property Build : Boolean read FBuild write FSetBuild;
|
|
|
|
property NumberOfPoints : Integer read FGetNumberOfPoints;
|
|
|
|
property Capacity : Integer read FCapacity write FSetCapacity;
|
|
|
|
property Fragments : integer read FFragments write FSetFragments;
|
|
|
|
property Interpolated : Boolean read FInterpolated write FSetInterpolated;
|
|
|
|
property NumberOfVertices : Integer read FGetNumberOfVertices;
|
|
|
|
property Point [Index:Integer] : TVertex read FGetPoint write FSetPoint;
|
|
|
|
property Vertex[Index:Integer] : TVertex read FGetVertex write FSetVertex;
|
|
|
|
property Knuckle[Index:integer] : Boolean read FGetKnuckle write FSetKnuckle;
|
|
|
|
property Min : TVertex read FMin;
|
|
|
|
property Max : TVertex read FMax;
|
|
|
|
property ShowVertices : boolean read FShowVertices write FShowVertices;
|
|
|
|
property Color : TColor read FColor write FColor;
|
|
|
|
end;
|
|
|
|
|
|
|
|
implementation
|
|
|
|
|
|
|
|
// The following tpes are used for the interpolation routines
|
|
|
|
Type TMatrixRow = Array[1..1] of TDataType;
|
|
|
|
PMatrixRow = ^TMatrixRow;
|
|
|
|
TMatrix = array[1..MaxInterpolatedVertices] of PMatrixRow;
|
|
|
|
|
|
|
|
var Matrix : TMatrix;
|
|
|
|
|
|
|
|
|
|
|
|
function DistPP2D(P1,P2:TVertex):TDataType;
|
|
|
|
begin
|
|
|
|
Result:=Sqrt(Sqr(P2.X-P1.X)+Sqr(P2.Y-P1.Y));
|
|
|
|
end;{DistPP2D}
|
|
|
|
|
|
|
|
{--------------------------------------------------------------------------------------------------}
|
|
|
|
{ TBSpline }
|
|
|
|
{--------------------------------------------------------------------------------------------------}
|
|
|
|
procedure TBSpline.FSetBuild(val:boolean);
|
|
|
|
begin
|
|
|
|
if not val then
|
|
|
|
begin
|
|
|
|
// Release allocated memory for vertices
|
|
|
|
if (FVertexList<>nil) and (FBuild) then Freemem(FVertexList,(FNoVertices+2)*SizeOf(TVertex));
|
|
|
|
FNoVertices:=0;
|
|
|
|
FVertexList:=nil;
|
|
|
|
// Clear extents
|
|
|
|
FMin.X:=0;
|
|
|
|
FMin.Y:=0;
|
|
|
|
FMax.X:=1;
|
|
|
|
FMax.Y:=1;
|
|
|
|
end;
|
|
|
|
FBuild:=Val;
|
|
|
|
end;{TBSpline.FSetBuild}
|
|
|
|
|
|
|
|
procedure TBSpline.FSetCapacity(Val:Integer);
|
|
|
|
var CurrentSize : Word;
|
|
|
|
NewSize : Word;
|
|
|
|
OldPoints : P2DPointList;
|
|
|
|
OldKnuckle : PKnuckleList;
|
|
|
|
begin
|
|
|
|
if Val<>FCapacity then
|
|
|
|
begin
|
|
|
|
CurrentSize:=Capacity*SizeOf(TVertex);
|
|
|
|
NewSize:=Val*SizeOf(TVertex);
|
|
|
|
OldPoints:=FPointList;
|
|
|
|
FPointList:=nil;
|
|
|
|
OldKnuckle:=FKnuckleList;
|
|
|
|
FKnuckleList:=Nil;
|
|
|
|
if Val>0 then
|
|
|
|
begin
|
|
|
|
GetMem(FPointList,NewSize);
|
|
|
|
GetMem(FKnuckleList,Val);
|
|
|
|
FillChar(FKnuckleList^,Val,0);
|
|
|
|
if Capacity<>0 then
|
|
|
|
begin
|
|
|
|
Move(OldKnuckle^,FKnuckleList^,Capacity);
|
|
|
|
Move(OldPoints^,FPointList^,CurrentSize);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
if CurrentSize<>0 then
|
|
|
|
begin
|
|
|
|
Freemem(OldPoints,CurrentSize);
|
|
|
|
Freemem(OldKnuckle,Capacity);
|
|
|
|
end;
|
|
|
|
FCapacity:=Val;
|
|
|
|
end;
|
|
|
|
end;{TBSpline.FSetCapacity}
|
|
|
|
|
|
|
|
procedure TBSpline.FSetFragments(Const value:integer);
|
|
|
|
begin
|
|
|
|
if FFragments<>value then
|
|
|
|
begin
|
|
|
|
FFragments:=value;
|
|
|
|
if FFragments>MaxFragments then FFragments:=MaxFragments;
|
|
|
|
end;
|
|
|
|
end;{TBSpline.FSetFragments}
|
|
|
|
|
|
|
|
procedure TBSpline.FSetInterpolated(const value:boolean);
|
|
|
|
begin
|
|
|
|
if Value<>FInterpolated then
|
|
|
|
begin
|
|
|
|
FInterpolated:=value;
|
|
|
|
Build:=false;
|
|
|
|
end;
|
|
|
|
end;{TBSpline.FSetInterpolated}
|
|
|
|
|
|
|
|
function TBSpline.FGetNumberOfPoints:integer;
|
|
|
|
begin
|
|
|
|
Result:=FNoPoints;
|
|
|
|
end;{TBSpline.FGetNumberOfPoints}
|
|
|
|
|
|
|
|
function TBSpline.FGetPoint(Index:Integer):TVertex;
|
|
|
|
begin
|
|
|
|
if (Index>=1) and (Index<=FNoPoints) then Result:=FPointList^[Index] else
|
|
|
|
begin
|
|
|
|
Result.X:=0;
|
|
|
|
Result.Y:=0;
|
|
|
|
Result.Y:=0;
|
|
|
|
Raise Exception.Create('List index out of bounds in '+Self.ClassName+'.FGetPoint. ('+IntToStr(Index)+').');
|
|
|
|
end;
|
|
|
|
end;{TBSpline.FGetPoint}
|
|
|
|
|
|
|
|
procedure TBSpline.FSetPoint(Index:Integer;Value:TVertex);
|
|
|
|
begin
|
|
|
|
if (Index>=1) and (Index<=FNoPoints) then
|
|
|
|
begin
|
|
|
|
FPointList^[Index]:=Value;
|
|
|
|
Build:=False;
|
|
|
|
end else Raise Exception.Create('List index out of bounds in '+Self.ClassName+'.FSetPoint. ('+IntToStr(Index)+').');
|
|
|
|
end;{TBSpline.FSetPoint}
|
|
|
|
|
|
|
|
function TBSpline.FGetVertex(Index:Integer):TVertex;
|
|
|
|
begin
|
|
|
|
Result.X:=0;
|
|
|
|
Result.Y:=0;
|
|
|
|
if FBuild=False then if FNoPoints>1 then Rebuild
|
|
|
|
else exit;
|
|
|
|
if (Index>=0) and (Index<=NumberOfVertices+1) then Result:=FVertexList^[Index]
|
|
|
|
else Raise Exception.Create('List index out of bounds in '+Self.ClassName+'.FGetVertex. ('+IntToStr(Index)+').');
|
|
|
|
end;{TBSpline.FGetVertex}
|
|
|
|
|
|
|
|
procedure TBSpline.FSetVertex(Index:Integer;Value:TVertex);
|
|
|
|
begin
|
|
|
|
if (Index>=0) and (Index<=NumberOfVertices+1) then FVertexList^[Index]:=Value
|
|
|
|
else Raise Exception.Create('List index out of bounds in '+Self.ClassName+'.FSetVertex. ('+IntToStr(Index)+').');
|
|
|
|
end;{TBSpline.FSetVertex}
|
|
|
|
|
|
|
|
function TBSpline.FGetKnuckle(Index:integer):Boolean;
|
|
|
|
begin
|
|
|
|
if (Index=1) or (Index=FNoPoints) then Result:=false else
|
|
|
|
if (Index>0) and (Index<=FNoPoints) then Result:=FKnuckleList^[Index]
|
|
|
|
else Raise Exception.Create('List index out of bounds in '+Self.ClassName+'FGetKnuckle. ('+IntToStr(Index)+').');
|
|
|
|
end;{TBSpline.FGetKnuckle}
|
|
|
|
|
|
|
|
procedure TBSpline.FSetKnuckle(Index:integer;Value:Boolean);
|
|
|
|
begin
|
|
|
|
if (Index>0) and (Index<=FNoPoints) then
|
|
|
|
begin
|
|
|
|
FKnuckleList^[Index]:=Value;
|
|
|
|
Build:=false;
|
|
|
|
end else Raise Exception.Create('List index out of bounds in '+Self.ClassName+'.FSetKnuckle. ('+IntToStr(Index)+').');
|
|
|
|
end;{TBSpline.FSetKnuckle}
|
|
|
|
|
|
|
|
function TBSpline.FGetNumberOfVertices:integer;
|
|
|
|
begin
|
|
|
|
if Not FBuild then Rebuild;
|
|
|
|
Result:=FNoVertices;
|
|
|
|
end;{TBSpline.FGetNumberOfVertices}
|
|
|
|
|
|
|
|
procedure TBSpline.Rebuild;
|
|
|
|
var I,J : integer;
|
|
|
|
Vertex2D : TVertex;
|
|
|
|
begin
|
|
|
|
if FNoPoints>1 then
|
|
|
|
begin
|
|
|
|
if FVertexList<>nil then
|
|
|
|
begin
|
|
|
|
Freemem(FVertexList,(FNoVertices+2)*SizeOf(TVertex));
|
|
|
|
FVertexList:=nil;
|
|
|
|
end;
|
|
|
|
FNoVertices:=0;
|
|
|
|
For I:=1 to FNoPoints do if Knuckle[I] then inc(FNoVertices,3)
|
|
|
|
else inc(FNoVertices,1);
|
|
|
|
GetMem(FVertexList,(FNoVertices+2)*SizeOf(TVertex));
|
|
|
|
J:=0;
|
|
|
|
For I:=1 to FNoPoints do
|
|
|
|
begin
|
|
|
|
Vertex2D:=Point[I];
|
|
|
|
if Knuckle[I] then
|
|
|
|
begin
|
|
|
|
FVertexList^[J+1]:=Vertex2D;
|
|
|
|
FVertexList^[J+2]:=Vertex2D;
|
|
|
|
Inc(J,2);
|
|
|
|
end;
|
|
|
|
FVertexList^[J+1]:=FPointList^[I];
|
|
|
|
if I=1 then
|
|
|
|
begin
|
|
|
|
FMin:=Vertex2D;
|
|
|
|
FMax:=FMin;
|
|
|
|
end else
|
|
|
|
begin
|
|
|
|
if Vertex2D.X<FMin.X then FMin.X:=Vertex2D.X;
|
|
|
|
if Vertex2D.Y<FMin.Y then FMin.Y:=Vertex2D.Y;
|
|
|
|
if Vertex2D.X>FMax.X then FMax.X:=Vertex2D.X;
|
|
|
|
if Vertex2D.Y>FMax.Y then FMax.Y:=Vertex2D.Y;
|
|
|
|
end;
|
|
|
|
|
|
|
|
Inc(J);
|
|
|
|
end;
|
|
|
|
if Interpolated then
|
|
|
|
begin
|
|
|
|
For I:=1 to FNoVertices do
|
|
|
|
begin
|
|
|
|
GetMem(Matrix[I],FNovertices*SizeOf(TDatatype));
|
|
|
|
FillChar(Matrix[I]^,FNovertices*SizeOf(TDatatype),0);
|
|
|
|
end;
|
|
|
|
FFillMatrix;
|
|
|
|
Finterpolate;
|
|
|
|
For I:=1 to FNoVertices do
|
|
|
|
begin
|
|
|
|
FreeMem(Matrix[I],FNovertices*SizeOf(TDatatype));
|
|
|
|
Matrix[I]:=nil;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
FBuild:=true;
|
|
|
|
FPhantomPoints;
|
|
|
|
end;{TBSpline.Rebuild}
|
|
|
|
|
|
|
|
procedure TBSpline.FInterpolate;
|
|
|
|
var I,J,K : integer;
|
|
|
|
Factor : extended;
|
|
|
|
Tmp : P2DVertexList;
|
|
|
|
begin
|
|
|
|
if (FNoVertices<MaxInterpolatedVertices) and (FNoVertices>2) then
|
|
|
|
begin
|
|
|
|
GetMem(Tmp,(FNoVertices+2)*SizeOf(TVertex));
|
|
|
|
for I:=1 to FNoVertices do
|
|
|
|
begin
|
|
|
|
for J:=I+1 to FNoVertices do
|
|
|
|
begin
|
|
|
|
factor:=Matrix[J]^[I]/Matrix[I]^[I];
|
|
|
|
for K:=1 to FNoVertices do Matrix[J]^[K]:=Matrix[J]^[K]-factor*Matrix[I]^[K];
|
|
|
|
FVertexList^[J].x:=FVertexList^[J].x-factor*FVertexList^[J-1].x;
|
|
|
|
FVertexList^[J].y:=FVertexList^[J].y-factor*FVertexList^[J-1].y;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
Tmp^[FNoVertices].x:=FVertexList^[FNoVertices].x/Matrix[FNoVertices]^[FNoVertices];
|
|
|
|
Tmp^[FNoVertices].y:=FVertexList^[FNoVertices].y/Matrix[FNoVertices]^[FNoVertices];
|
|
|
|
for I:=FNoVertices-1 downto 1 do
|
|
|
|
begin
|
|
|
|
Tmp^[I].x:=(1/Matrix[I]^[I])*(FVertexList^[I].x-Matrix[I]^[I+1]*Tmp^[I+1].x);
|
|
|
|
Tmp^[I].y:=(1/Matrix[I]^[I])*(FVertexList^[I].y-Matrix[I]^[I+1]*Tmp^[I+1].y);
|
|
|
|
end;
|
|
|
|
if FVertexList<>nil then
|
|
|
|
begin
|
|
|
|
Freemem(FVertexList,(FNoVertices+2)*SizeOf(TVertex));
|
|
|
|
FVertexList:=nil;
|
|
|
|
end;
|
|
|
|
FVertexList:=Tmp;
|
|
|
|
end;
|
|
|
|
end;{TBSpline.FInterpolate}
|
|
|
|
|
|
|
|
function TBSpline.KnownX(XValue:TDataType;var Results:T2DResults):Boolean;
|
|
|
|
var UpperLimit : integer;
|
|
|
|
Counter : integer;
|
|
|
|
mn, mx : extended;
|
|
|
|
Parameter : extended;
|
|
|
|
Error : extended;
|
|
|
|
Finished : boolean;
|
|
|
|
P1,P2 : TVertex;
|
|
|
|
Output : TVertex;
|
|
|
|
begin
|
|
|
|
Result:=false;
|
|
|
|
if not FBuild then
|
|
|
|
begin
|
|
|
|
Rebuild;
|
|
|
|
if not FBuild then exit;
|
|
|
|
end;
|
|
|
|
Results.NumberOfHits:=0;
|
|
|
|
if NumberOfPoints=0 then exit;
|
|
|
|
if NumberOfPoints=2 then
|
|
|
|
begin
|
|
|
|
P1:=Point[1];
|
|
|
|
P2:=Point[2];
|
|
|
|
if P1.X>P2.X then
|
|
|
|
begin
|
|
|
|
Output:=P1;
|
|
|
|
P1:=P2;
|
|
|
|
P2:=Output;
|
|
|
|
end;
|
|
|
|
if (P1.X<=XValue) and (P2.X>=XValue) then
|
|
|
|
begin
|
|
|
|
if abs(P1.X-P2.X)<1e6 then Parameter:=0.5
|
|
|
|
else Parameter:=(XValue-P1.X)/(P2.X-P1.X);
|
|
|
|
Results.NumberOfHits:=Results.NumberOfHits+1;
|
|
|
|
Results.Parameter[Results.NumberOfHits]:=Parameter;
|
|
|
|
Results.Point[Results.NumberOfHits]:=Value(Parameter);
|
|
|
|
Results.Point[Results.NumberOfHits].X:=XValue;
|
|
|
|
end;
|
|
|
|
end else
|
|
|
|
begin
|
|
|
|
UpperLimit:=1;
|
|
|
|
P1:=Point[1];
|
|
|
|
repeat
|
|
|
|
Finished:=false;
|
|
|
|
repeat
|
|
|
|
P2:=Value(UpperLimit/MaxCalcSteps);
|
|
|
|
if ((P1.X<=XValue) and (P2.X>=XValue)) or
|
|
|
|
((P1.X>=XValue) and (P2.X<=XValue)) then Finished:=true;
|
|
|
|
if not Finished then
|
|
|
|
begin
|
|
|
|
if UpperLimit=MaxCalcSteps then
|
|
|
|
begin
|
|
|
|
Result:=Results.NumberOfHits>0;
|
|
|
|
exit;
|
|
|
|
end else
|
|
|
|
begin
|
|
|
|
inc(UpperLimit);
|
|
|
|
P1:=P2;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
until Finished;
|
|
|
|
mx:=UpperLimit/MaxCalcSteps;
|
|
|
|
mn:=(UpperLimit-1)/MaxCalcSteps;
|
|
|
|
Counter:=0;
|
|
|
|
repeat
|
|
|
|
if abs(P1.X-P2.X)<1e-6 then Parameter:=0.5*(mn+mx) else
|
|
|
|
if (P1.X<=XValue) and (P2.X>=XValue) then Parameter:=mn+(mx-mn)*((XValue-P1.X)/(P2.X-P1.X))
|
|
|
|
else Parameter:=mn+((P1.X-XValue)*(mx-mn)/(P1.X-P2.X));
|
|
|
|
if Parameter<mn then Parameter:=mn;
|
|
|
|
if Parameter>mx then Parameter:=mx;
|
|
|
|
Output:=Value(parameter);
|
|
|
|
if XValue=0 then Error:=abs(Output.X-XValue) else
|
|
|
|
Error:=abs((XValue-Output.X)/XValue);
|
|
|
|
if Output.X>XValue then
|
|
|
|
begin
|
|
|
|
if (P1.X<=XValue) and (P2.X>=XValue) then
|
|
|
|
begin
|
|
|
|
mx:=parameter;
|
|
|
|
P2:=Output;
|
|
|
|
end else
|
|
|
|
begin
|
|
|
|
mn:=parameter;
|
|
|
|
P1:=Output;
|
|
|
|
end;
|
|
|
|
end else
|
|
|
|
begin
|
|
|
|
if (P1.X<=XValue) and (P2.X>=XValue) then
|
|
|
|
begin
|
|
|
|
mn:=parameter;
|
|
|
|
P1:=Output;
|
|
|
|
end else
|
|
|
|
begin
|
|
|
|
mx:=parameter;
|
|
|
|
P2:=Output;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
inc(Counter);
|
|
|
|
until (Error<MaxError) or (Counter>MaxIterations);
|
|
|
|
if Results.NumberOfHits>=MaxResults then Raise Exception.Create('Max. number of results exceeded in TBSpline.KnownX');
|
|
|
|
Results.NumberOfHits:=Results.NumberOfHits+1;
|
|
|
|
Results.Parameter[Results.NumberOfHits]:=Parameter;
|
|
|
|
Results.Point[Results.NumberOfHits]:=Value(Parameter);
|
|
|
|
Results.Point[Results.NumberOfHits].X:=XValue;
|
|
|
|
mx:=UpperLimit/MaxCalcSteps;
|
|
|
|
P1:=Value(mx);
|
|
|
|
Inc(UpperLimit);
|
|
|
|
until UpperLimit>MaxCalcSteps;
|
|
|
|
end;
|
|
|
|
Result:=Results.NumberOfHits>0;
|
|
|
|
end;{TBSpline.KnownX}
|
|
|
|
|
|
|
|
function TBSpline.KnownY(YValue:TDataType;var Results:T2DResults):Boolean;
|
|
|
|
var UpperLimit : integer;
|
|
|
|
Counter : integer;
|
|
|
|
mn, mx : extended;
|
|
|
|
Parameter : extended;
|
|
|
|
Error : extended;
|
|
|
|
Finished : boolean;
|
|
|
|
P1,P2 : TVertex;
|
|
|
|
Output : TVertex;
|
|
|
|
begin
|
|
|
|
Result:=false;
|
|
|
|
if not FBuild then
|
|
|
|
begin
|
|
|
|
Rebuild;
|
|
|
|
if not FBuild then exit;
|
|
|
|
end;
|
|
|
|
Results.NumberOfHits:=0;
|
|
|
|
if NumberOfPoints=0 then exit;
|
|
|
|
if NumberOfPoints=2 then
|
|
|
|
begin
|
|
|
|
P1:=Point[1];
|
|
|
|
P2:=Point[2];
|
|
|
|
if P1.Y>P2.Y then
|
|
|
|
begin
|
|
|
|
Output:=P1;
|
|
|
|
P1:=P2;
|
|
|
|
P2:=Output;
|
|
|
|
end;
|
|
|
|
if (P1.Y<=YValue) and (P2.Y>=YValue) then
|
|
|
|
begin
|
|
|
|
if abs(P1.Y-P2.Y)<1e6 then Parameter:=0.5
|
|
|
|
else Parameter:=(YValue-P1.Y)/(P2.Y-P1.Y);
|
|
|
|
Results.NumberOfHits:=Results.NumberOfHits+1;
|
|
|
|
Results.Parameter[Results.NumberOfHits]:=Parameter;
|
|
|
|
Results.Point[Results.NumberOfHits]:=Value(Parameter);
|
|
|
|
Results.Point[Results.NumberOfHits].Y:=YValue;
|
|
|
|
end;
|
|
|
|
end else
|
|
|
|
begin
|
|
|
|
UpperLimit:=1;
|
|
|
|
P1:=Point[1];
|
|
|
|
repeat
|
|
|
|
Finished:=false;
|
|
|
|
repeat
|
|
|
|
P2:=Value(UpperLimit/MaxCalcSteps);
|
|
|
|
if ((P1.Y<=YValue) and (P2.Y>=YValue)) or
|
|
|
|
((P1.Y>=YValue) and (P2.Y<=YValue)) then Finished:=true;
|
|
|
|
if not Finished then
|
|
|
|
begin
|
|
|
|
if UpperLimit=MaxCalcSteps then
|
|
|
|
begin
|
|
|
|
Result:=Results.NumberOfHits>0;
|
|
|
|
exit;
|
|
|
|
end else
|
|
|
|
begin
|
|
|
|
inc(UpperLimit);
|
|
|
|
P1:=P2;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
until Finished;
|
|
|
|
mx:=UpperLimit/MaxCalcSteps;
|
|
|
|
mn:=(UpperLimit-1)/MaxCalcSteps;
|
|
|
|
Counter:=0;
|
|
|
|
repeat
|
|
|
|
if abs(P1.Y-P2.Y)<1e-6 then Parameter:=0.5*(mn+mx) else
|
|
|
|
if (P1.Y<=YValue) and (P2.Y>=YValue) then Parameter:=mn+(mx-mn)*((YValue-P1.Y)/(P2.Y-P1.Y))
|
|
|
|
else Parameter:=mn+((P1.Y-YValue)*(mx-mn)/(P1.Y-P2.Y));
|
|
|
|
if Parameter<mn then Parameter:=mn;
|
|
|
|
if Parameter>mx then Parameter:=mx;
|
|
|
|
Output:=Value(parameter);
|
|
|
|
if YValue=0 then Error:=abs(Output.Y-YValue) else
|
|
|
|
Error:=abs((YValue-Output.Y)/YValue);
|
|
|
|
if Output.Y>YValue then
|
|
|
|
begin
|
|
|
|
if (P1.Y<=YValue) and (P2.Y>=YValue) then
|
|
|
|
begin
|
|
|
|
mx:=parameter;
|
|
|
|
P2:=Output;
|
|
|
|
end else
|
|
|
|
begin
|
|
|
|
mn:=parameter;
|
|
|
|
P1:=Output;
|
|
|
|
end;
|
|
|
|
end else
|
|
|
|
begin
|
|
|
|
if (P1.Y<=YValue) and (P2.Y>=YValue) then
|
|
|
|
begin
|
|
|
|
mn:=parameter;
|
|
|
|
P1:=Output;
|
|
|
|
end else
|
|
|
|
begin
|
|
|
|
mx:=parameter;
|
|
|
|
P2:=Output;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
inc(Counter);
|
|
|
|
until (Error<MaxError) or (Counter>MaxIterations);
|
|
|
|
if Results.NumberOfHits>=MaxResults then Raise Exception.Create('Max. number of results exceeded in TBSpline.KnownY');
|
|
|
|
Results.NumberOfHits:=Results.NumberOfHits+1;
|
|
|
|
Results.Parameter[Results.NumberOfHits]:=Parameter;
|
|
|
|
Results.Point[Results.NumberOfHits]:=Value(Parameter);
|
|
|
|
Results.Point[Results.NumberOfHits].Y:=YValue;
|
|
|
|
mx:=UpperLimit/MaxCalcSteps;
|
|
|
|
P1:=Value(mx);
|
|
|
|
Inc(UpperLimit);
|
|
|
|
until UpperLimit>MaxCalcSteps;
|
|
|
|
end;
|
|
|
|
Result:=Results.NumberOfHits>0;
|
|
|
|
end;{TBSpline.KnownY}
|
|
|
|
|
|
|
|
procedure TBSpline.AddPoint(Vertex:TVertex);
|
|
|
|
begin
|
|
|
|
if NumberOfPoints=Capacity then Capacity:=Capacity+VerticesIncrement;
|
|
|
|
inc(FNoPoints);
|
|
|
|
Point[NumberOfPoints]:=Vertex;
|
|
|
|
Build:=false;
|
|
|
|
end;{TBSpline.AddPoint}
|
|
|
|
|
|
|
|
constructor TBSpline.Create;
|
|
|
|
begin
|
|
|
|
inherited Create;
|
|
|
|
FPointList:=nil;
|
|
|
|
FVertexList:=nil;
|
|
|
|
FKnuckleList:=nil;
|
|
|
|
FCapacity:=0;
|
|
|
|
Clear;
|
|
|
|
end;{TBSpline.Create}
|
|
|
|
|
|
|
|
procedure TBSpline.DeletePoint(Index:Integer);
|
|
|
|
var I : integer;
|
|
|
|
begin
|
|
|
|
if NumberOfPoints>0 then
|
|
|
|
begin
|
|
|
|
dec(FNoPoints);
|
|
|
|
for I:=Index to NumberOfPoints do
|
|
|
|
begin
|
|
|
|
FPointList^[I]:=FPointList^[I+1];
|
|
|
|
FKnuckleList^[I]:=FKnuckleList^[I+1];
|
|
|
|
end;
|
|
|
|
Build:=false;
|
|
|
|
end;
|
|
|
|
if NumberOfPoints=0 then Clear;
|
|
|
|
end;{TBSpline.DeletePoint}
|
|
|
|
|
|
|
|
destructor TBSpline.Destroy;
|
|
|
|
begin
|
|
|
|
Clear;
|
|
|
|
inherited Destroy;
|
|
|
|
end;{TBSpline.Destroy}
|
|
|
|
|
|
|
|
procedure TBSpline.Draw(Canvas:TCanvas);
|
|
|
|
var J : Integer;
|
|
|
|
ParameterValue : single;
|
|
|
|
V : TVertex;
|
|
|
|
begin
|
|
|
|
//Canvas.Pen.Color:=Color;
|
|
|
|
For J:=0 to Fragments do {Draw the spline in 200 steps}
|
|
|
|
begin
|
|
|
|
ParameterValue:=(J/Fragments); // parameter value must be in the range 0.0-1.0
|
|
|
|
V:=Value(ParameterValue);
|
|
|
|
// Use used moveto/lineto method for demo-drawing.
|
|
|
|
// using the Canvas.polyline method is SIGNIFICANTLY FASTER though!!
|
|
|
|
if J=0 then Canvas.MoveTo(Round(V.X),Round(V.Y))
|
|
|
|
else Canvas.LineTo(Round(V.X),Round(V.Y));
|
|
|
|
end;
|
|
|
|
if ShowVertices then For J:=1 to NumberOfPoints do {Draw the vertices}
|
|
|
|
begin
|
|
|
|
V:=Point[J];
|
|
|
|
Canvas.Pen.Color:=clRed;
|
|
|
|
Canvas.Ellipse(Round(V.X)-2,Round(V.Y)-2,Round(V.X)+2,Round(V.Y)+2);
|
|
|
|
end;
|
|
|
|
end;{TBSpline.Draw}
|
|
|
|
|
|
|
|
procedure TBSpline.Extents(Var Min,Max : TVertex);
|
|
|
|
var I : Integer;
|
|
|
|
P : TVertex;
|
|
|
|
begin
|
|
|
|
for I:=1 to NumberOfPoints do
|
|
|
|
begin
|
|
|
|
P:=Point[I];
|
|
|
|
if P.X<Min.X then Min.X:=P.X;
|
|
|
|
if P.X>Max.X then Max.X:=P.X;
|
|
|
|
if P.Y<Min.Y then Min.Y:=P.Y;
|
|
|
|
if P.Y>Max.Y then Max.Y:=P.Y;
|
|
|
|
end;
|
|
|
|
end;{TBSpline.Extents}
|
|
|
|
|
|
|
|
function TBSpline.FirstDerive(Parameter:extended):TVertex;
|
|
|
|
var c,S,E : integer;
|
|
|
|
Dist : extended;
|
|
|
|
Mix : extended;
|
|
|
|
Mid : Extended;
|
|
|
|
begin
|
|
|
|
Result.X:=0;
|
|
|
|
Result.Y:=0;
|
|
|
|
if FNoPoints<2 then exit;
|
|
|
|
if not FBuild then Rebuild;
|
|
|
|
Mid:=(NumberOfVertices-1)*Parameter+1;
|
|
|
|
S:=Trunc(Mid-1);
|
|
|
|
if S<0 then S:=0;
|
|
|
|
E:=S+3;
|
|
|
|
if S>FNovertices+1 then S:=FNovertices+1;
|
|
|
|
for c:=S to E do
|
|
|
|
begin
|
|
|
|
dist:=C-Mid;
|
|
|
|
if (dist>-2) and (dist<=-1) then Mix:=(2+dist)*(2+dist)*0.5 else
|
|
|
|
if (dist>=-1) and (dist<=0) then Mix:=(-2*dist-1.5*dist*dist) else
|
|
|
|
if (dist>=0) and (dist<=1) then Mix:=(-2*dist+1.5*dist*dist) else
|
|
|
|
if (dist>=1) and (dist<2) then Mix:=-(2-dist)*(2-dist)*0.5 else
|
|
|
|
mix:=0;
|
|
|
|
Result.x:=Result.x+FVertexList^[c].x*mix;
|
|
|
|
Result.y:=Result.y+FVertexList^[c].y*mix;
|
|
|
|
end;
|
|
|
|
end;{TBSpline.FirstDerive}
|
|
|
|
|
|
|
|
procedure TBSpline.InsertPoint(Index:Integer;Vertex:TVertex);
|
|
|
|
var I : integer;
|
|
|
|
begin
|
|
|
|
if (Index>=0) and (Index<=NumberOfPoints) then
|
|
|
|
begin
|
|
|
|
if NumberOfPoints=Capacity then Capacity:=Capacity+VerticesIncrement;
|
|
|
|
inc(FNoPoints);
|
|
|
|
for I:=NumberOfPoints downto Index+1 do
|
|
|
|
begin
|
|
|
|
FPointList^[I]:=FPointList^[I-1];
|
|
|
|
FKnuckleList^[I]:=FKnuckleList^[I-1];
|
|
|
|
end;
|
|
|
|
FPointList^[Index]:=Vertex;
|
|
|
|
FKnuckleList^[Index]:=False;
|
|
|
|
Build:=false;
|
|
|
|
end else raise Exception.Create('Index out of range');
|
|
|
|
end;{TBSpline.InsertPoint}
|
|
|
|
|
|
|
|
procedure TBSpline.Invert;
|
|
|
|
var OldPoints : P2DPointList;
|
|
|
|
OldVertices : P2DVertexList;
|
|
|
|
OldKnuckle : PKnuckleList;
|
|
|
|
I : Integer;
|
|
|
|
begin
|
|
|
|
exit; // Backup current data
|
|
|
|
OldPoints:=FPointList;
|
|
|
|
FPointList:=nil;
|
|
|
|
OldKnuckle:=FKnuckleList;
|
|
|
|
FKnuckleList:=Nil;
|
|
|
|
OldVertices:=FVertexList;
|
|
|
|
FVertexList:=nil;
|
|
|
|
// Prepare new arrays
|
|
|
|
GetMem(FPointList,Capacity*SizeOf(TVertex));
|
|
|
|
GetMem(FKnuckleList,Capacity);
|
|
|
|
GetMem(FVertexList,(Capacity+2)*SizeOf(TVertex));
|
|
|
|
// Initialize knuckle list
|
|
|
|
FillChar(FKnuckleList^,Capacity,0);
|
|
|
|
// Copy controlpoints
|
|
|
|
for I:=1 to NumberOfPoints do
|
|
|
|
begin
|
|
|
|
FPointList^[I]:=OldPoints^[NumberOfPoints-I+1];
|
|
|
|
FKnuckleList^[I]:=OldKnuckle^[NumberOfPoints-I+1];
|
|
|
|
end;
|
|
|
|
// Copy vertices
|
|
|
|
if (OldVertices<>nil) and (FNoVertices<>0) and (Build) then for I:=0 to NumberOfVertices+1 do FVertexList^[I]:=OldVertices^[FNoVertices-I+1];
|
|
|
|
// Destroy old arrays
|
|
|
|
FreeMem(OldPoints,Capacity*SizeOf(TVertex));
|
|
|
|
if (Oldvertices<>nil) and (FNoVertices<>0) and (build) then FreeMem(OldVertices,(Capacity+2)*SizeOf(TVertex));
|
|
|
|
FreeMem(OldKnuckle,Capacity);
|
|
|
|
end;{TBSpline.Invert}
|
|
|
|
|
|
|
|
procedure TBSpline.Clear;
|
|
|
|
begin
|
|
|
|
FColor:=clBlack;
|
|
|
|
if (FVertexList<>nil) and (NumberOfVertices>0) then
|
|
|
|
begin
|
|
|
|
Freemem(FVertexList,(FNoVertices+2)*SizeOf(TVertex));
|
|
|
|
FVertexList:=nil;
|
|
|
|
end;
|
|
|
|
FShowvertices:=False;
|
|
|
|
FNoPoints:=0;
|
|
|
|
FNoVertices:=0;
|
|
|
|
Build:=False;
|
|
|
|
Capacity:=0;
|
|
|
|
FInterpolated:=false;
|
|
|
|
FFragments:=100;
|
|
|
|
end;{TBSpline.Clear}
|
|
|
|
|
|
|
|
procedure TBSpline.FPhantomPoints;
|
|
|
|
var I : integer;
|
|
|
|
begin
|
|
|
|
if NumberOfVertices>1 then
|
|
|
|
begin
|
|
|
|
I:=0;
|
|
|
|
FVertexList^[I].X:=2*FVertexList^[I+1].X-FVertexList^[I+2].X;
|
|
|
|
FVertexList^[I].Y:=2*FVertexList^[I+1].Y-FVertexList^[I+2].Y;
|
|
|
|
FVertexList^[NumberOfVertices+1].X:=2*FVertexList^[NumberOfVertices].X-FVertexList^[NumberOfVertices-1].X;
|
|
|
|
FVertexList^[NumberOfVertices+1].Y:=2*FVertexList^[NumberOfVertices].Y-FVertexList^[NumberOfVertices-1].Y;
|
|
|
|
end;
|
|
|
|
end;{TBSpline.FPhantomPoints}
|
|
|
|
|
|
|
|
procedure TBSpline.FFillMatrix;
|
|
|
|
var I,J : integer;
|
|
|
|
begin
|
|
|
|
if (FNoVertices>2) and (FNoVertices<=MaxInterpolatedVertices) then
|
|
|
|
begin
|
|
|
|
for I:=2 to FNoVertices-1 do
|
|
|
|
begin
|
|
|
|
Matrix[I]^[I-1]:=1/6;
|
|
|
|
Matrix[I]^[I]:=2/3;
|
|
|
|
Matrix[I]^[I+1]:=1/6;
|
|
|
|
end;
|
|
|
|
Matrix[1]^[1]:=1;
|
|
|
|
Matrix[FNoVertices]^[FNoVertices]:=1;
|
|
|
|
I:=3;
|
|
|
|
while I<FNoVertices-1 do
|
|
|
|
begin
|
|
|
|
if (abs(FVertexList^[I].X-FVertexList^[I-1].X)<1e-5) and (abs(FVertexList^[I+1].X-FVertexList^[I].X)<1e-5) and
|
|
|
|
(abs(FVertexList^[I].Y-FVertexList^[I-1].Y)<1e-5) and (abs(FVertexList^[I+1].Y-FVertexList^[I].Y)<1e-5) then
|
|
|
|
begin
|
|
|
|
for J:=I-1 to I+1 do
|
|
|
|
begin
|
|
|
|
Matrix[J]^[J-1]:=0;
|
|
|
|
Matrix[J]^[J]:=1;
|
|
|
|
Matrix[J]^[J+1]:=0;
|
|
|
|
end;
|
|
|
|
inc(I,2);
|
|
|
|
end else inc(I);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;{TBSpline.FFillMatrix}
|
|
|
|
|
|
|
|
function TBSpline.Value(Parameter:extended):TVertex;
|
|
|
|
var c,S,E : integer;
|
|
|
|
Dist : extended;
|
|
|
|
Mix : extended;
|
|
|
|
Mid : TDataType;
|
|
|
|
begin
|
|
|
|
Result.X:=0;
|
|
|
|
Result.Y:=0;
|
|
|
|
if FNoPoints<2 then exit;
|
|
|
|
if not FBuild then Rebuild;
|
|
|
|
Mid:=(NumberOfVertices-1)*Parameter+1;
|
|
|
|
S:=Trunc(Mid-1);
|
|
|
|
if S<0 then S:=0;
|
|
|
|
E:=S+3;
|
|
|
|
if S>FNovertices+1 then S:=FNovertices+1;
|
|
|
|
for c:=S to E do
|
|
|
|
begin
|
|
|
|
dist:=abs(C-Mid);
|
|
|
|
if dist<2 then
|
|
|
|
begin
|
|
|
|
if dist<1 then mix:=4/6-dist*dist+0.5*dist*dist*dist else
|
|
|
|
mix:=(2-dist)*(2-dist)*(2-dist)/6;
|
|
|
|
Result.x:=Result.x+FVertexList^[c].x*mix;
|
|
|
|
Result.y:=Result.y+FVertexList^[c].y*mix;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;{TBSpline.Value}
|
|
|
|
|
|
|
|
function TBSpline.secondDerive(Parameter:extended):TVertex;
|
|
|
|
var c,S,E : integer;
|
|
|
|
Dist : extended;
|
|
|
|
Mix : extended;
|
|
|
|
Mid : Extended;
|
|
|
|
begin
|
|
|
|
Result.X:=0;
|
|
|
|
Result.Y:=0;
|
|
|
|
if FNoPoints<2 then exit;
|
|
|
|
if not FBuild then Rebuild;
|
|
|
|
Mid:=(NumberOfVertices-1)*Parameter+1;
|
|
|
|
S:=Trunc(Mid-1);
|
|
|
|
if S<0 then S:=0;
|
|
|
|
E:=S+3;
|
|
|
|
if S>FNovertices+1 then S:=FNovertices+1;
|
|
|
|
for c:=S to E do
|
|
|
|
begin
|
|
|
|
dist:=C-Mid;
|
|
|
|
if (dist>=-2) and (dist<=-1) then Mix:=2+dist else
|
|
|
|
if (dist>=-1) and (dist<=0) then Mix:=-2-3*dist else
|
|
|
|
if (dist>=0) and (dist<=1) then Mix:=-2+3*dist else
|
|
|
|
if (dist>=1) and (dist<=2) then Mix:=2-dist else Mix:=0;
|
|
|
|
Result.x:=Result.x+FVertexList^[c].x*mix;
|
|
|
|
Result.y:=Result.y+FVertexList^[c].y*mix;
|
|
|
|
end;
|
|
|
|
end;{TBSpline.secondDerive}
|
|
|
|
|
|
|
|
end.
|