Delphi draw parabola with PlotGird - delphi

I am using a PlotGrid component with Firemonkey and I want to be able to draw a parabola in it. Given a very easy program that takes a,b,c as input ( > ax^2 + bx + c = 0) I am going to solve the 2nd degree equation.
As you should know that can be represented with a parabola. I thought that I could use this procedure:
procedure DrawArc(const Center, Radius: TPointF; StartAngle, SweepAngle: Single; const AOpacity: Single); overload;
You can see this picture to better understand the meaning:
I want to be able to draw a parabola in a PlotGrid because the user in this way I can have a "container" for my line. Also, if I resized the component or I am using a mobile device, I won't have to repaint the x/y axis. Is this a good way to go?
I have studied a bit the situation and this is what I have thought:
The focus coords of the parabola should be what in the procedure are called Center.
The Radius.X should be the value of one of the solutions of the equations. I The Radius.Y should be the position of the vertex.
I cannot figure how to setup the sweep angle. Is this too complicated? Should I find another better solution to draw the parabola?

You cannot draw parabola with ellipse arc, it doesn't give needed curve form (only small parabola piece near middle point might be approximated).
If Firemonkey graphics engine supports Bezier curves, you can draw exact parabola with Bezier. To calculate it's coefficients, express X- and Y- parts of parametric cubic curve in power basis
F(x) = -b/2a + (t - 0.5) * needed_parabola_X_range
F(y) = a*F(x)^2 + b * F(x) + c
then transform them into Bernstein polynomial basis (here you need some understanding of Bezier math)
VCL example
var
a, b, c, w: Double;
b2a, baw: Double;
P: array[0..3] of TPoint;
Cx, Cy: array[0..3] of Double;
begin
//vertex at (200, 100)
a := 0.05;
b := -20;
c := 2100;
W := 160; //width, distance between parabola ends
b2a := - 0.5 * b / a;
baw := b2a - 0.5 * W;
Cx[0] := baw;
Cx[1] := W;
Cx[2] := 0;
CX[3] := 0;
Cy[0] := a * baw * baw + c + b * baw;
Cy[1] := b * W + 2 * a * W * baW;
Cy[2] := a * W * W;
Cy[3] := 0;
p[0] := Point(Round(Cx[0]), Round(Cy[0]));
p[1].X := Round(cx[0] + cx[1] / 3);
p[1].Y := Round(cy[0] + cy[1] / 3);
p[2].X := Round(cx[0] + (2 * cx[1] + cx[2]) / 3);
p[2].Y := Round(cy[0] + (2 * cy[1] + cy[2]) / 3);
p[3].X := Round(cx[0] + cx[1] + cx[2] + cx[3]);
p[3].Y := Round(cy[0] + cy[1] + cy[2] + cy[3]);
Canvas.PolyBezier(P);

Related

How to draw a projectile following specific XY coordinates on a given bitmp under Delphi

In fact i want to draw an animated line from a given Tpoint coordinates to an other Tpoint on a Bitmap canvas for that i tried to read some open sources of OpenGl but task seems to be hard for my little competences in delphi i found this C++ source on github and its exactly what i want to do but under delphi 2009 if the line is animated it could be better for this personnal game prototype.
i tried some functions like -LineDDA- but its is very limited.
i look for somthing like this:
function Make_Projectile( canvas:tcanvas;Start,Target:Tpoint):boolean;
with canvas do
draw the animated_arc_line form start to trget;
if we toutch target then result= true else false...
end;
I tried this piece of code:
procedure ArcByStartEndAngle(Canvas:TCanvas;P0, P1: TPoint; Angle: Double; NSeg: Integer);
var
i: Integer;
len, dx, dy, mx, my, px, py, t, cx, cy, p0x, p0y, an: Double;
xx, yy: Integer;
begin
mx := (P0.x + P1.x) / 2;
my := (P0.y + P1.y) / 2;
dx := (P1.x - P0.x) / 2;
dy := (P1.y - P0.y) / 2;
len := Math.Hypot(dx, dy);
px := -dy / len;
py := dx / len;
if Angle = Pi then
t := 0
else
t := len / Math.Tan(Angle / 2);
cx := mx + px * t;
cy := my + py * t;
p0x := P0.x - cx;
p0y := P0.y - cy;
for i := 0 to NSeg + 1 do
begin
an := i * Angle / (NSeg + 1);
xx := Round(cx + p0x * Cos(an) - p0y * Sin(an));
yy := Round(cy + p0x * Sin(an) + p0y * Cos(an));
Canvas.Pen.Color:=clBlue;
Canvas.Brush.Color:=clRed;
Canvas.Ellipse(xx - 3, yy - 3, xx + 4, yy + 4);
end;
end;
and for these arguments it gives : this result
ArcByStartEndAngle(Form1.Image1.Canvas, Point(574,199),Point(62,202), Pi / 2, 8);
Please left a piece of code that can help or at list a translation of c code above for drawing the desired effect.
the following screenshoot could give an idea : ScreenShoot
enter image description here
**

Drawing oval path through four points

I have a four points that I want to draw oval that path through these four points
I do not want to use [UIBezierPath bezierPathWithOvalInRect:frame];
Because it can not work with rotated rectangle.
I suppose that your 4 points are not arbitrary ones, but middles of edges of rectangle around the ellipse.
You can build path from four Bezier curves to approximate zero-centered unit circle. Example of code here
Then apply to control points affine transformation that transform circle to rotated ellipse with semi-axes A and B, center (CX, CY) and rotation angle Alpha.
For your case, if points are P0..P3, then
A = |P0P2| (distance)
B = |P1P3|
CX = (P0.X + P2.X)/2
CY = (P0.Y + P2.Y)/2
Alpha = ArcTan2(P0.Y - P2.Y, P0.X - P2.X)
I have Delphi code for this problem, hope it could help. Note that closed Bezier path contains 13 points (the last is the same as the first).
procedure CalcRotatedEllipse(CX, CY, A, B: Integer; Alpha: Double; var BezPts: array of TPoint);
const
MP = 0.55228475;
var
CA, SA, ACA, ASA, BCA, BSA: Double;
i, CX2, CY2: Integer;
function TransformPoint(X, Y: Double): TPoint;
begin
Result.X := Round(CX + X * ACA + Y * BSA);
Result.Y := Round(CY - X * ASA + Y * BCA);
end;
begin
Assert(Length(BezPts) = 13);
CA:= Cos(Alpha); SA := Sin(Alpha);
ACA := A * CA; ASA := A * SA;
BCA := B * CA; BSA := B * SA;
CX2 := 2 * CX; CY2 := 2 * CY;
BezPts[0] := TransformPoint(1, 0);
BezPts[1] := TransformPoint(1, MP);
BezPts[2] := TransformPoint(MP, 1);
BezPts[3] := TransformPoint(0, 1);
BezPts[4] := TransformPoint(- MP, 1);
BezPts[5] := TransformPoint(-1, MP);
for i := 0 to 5 do
BezPts[i + 6] := Point(CX2 - BezPts[i].X, CY2 - BezPts[i].Y);
BezPts[12] := BezPts[0];
end;
procedure TForm1.Button3Click(Sender: TObject);
var
Pts: array[0..12] of TPoint;
begin
CalcRotatedEllipse(200, 200, 200, 70, Pi/6, Pts);
Canvas.PolyBezier(Pts);
end;

Uniformly (but not random) spaced set of n-points on an annulus

Good day,
I am currently facing a challenge at work which is becoming quite a pain to solve.
The Challenge:
I need to create CAD drawing of a hole pattern for a perforated annular plate which will be laser cut. The holes needs to be as close to equidistant as possible, allowing for even airflow distribution. Total area of the holes needs to equate to 40% of the total annular area. From my calculations I will need 10,190 holes of 6.35mm to accomplish this...
The Solution:
Write a small program to calculate the point set's XY coordinates which I can import into my CAD software.
The Question:
For this purpose I want to use a Fermat's Spiral approach to calculate the point coordinates. As a starting point, I used info from Marmakoide's Blog. So far I have the point distribution done evenly and looks to be approximately equidistantly spaced. My problem is that I need to, somehow, specify the inner and outer radii of the annulus wherein the point set of N-points must fall. Please not that I am not at all mathematically inclined, so please keep the answers as clear as possible.
This is my code:
procedure TForm1.btnCalcClick(Sender: TObject);
var golden_angle, radI, radO, rad, theta, x, y : Double;
i, k, holeQTY, index : integer;
xCoords, yCoords : Array of Double;
begin
holeQTY := StrToInt(edtNumHoles.Text);
golden_angle := Pi * (3 - Sqrt(5));
radI := StrToFloat(edtRadInner.Text);
radO := StrToFloat(edtRadOuter.Text);
SetLength(xCoords, holeQTY);
SetLength(yCoords, holeQTY);
for i := 0 to holeQTY - 1 do
begin
theta := i * golden_angle;
rad := Sqrt(i) / Sqrt(holeQTY);
x := rad * Cos(theta);
y := rad * Sin(theta);
xCoords[i] := x;
yCoords[i] := y;
StatusBar1.Panels[1].Text := IntToStr(i+1) + ' of ' + IntToStr(holeQTY);
StatusBar1.Update;
Application.ProcessMessages;
end;
for k := 0 to holeQTY - 1 do
begin
Chart1.Series[0].AddXY(xCoords[k], yCoords[k], '', clBlack);
end;
end;
Thanks in advance,
Martin
Edit: Normalization taken into account
Given in the Spreading points page approach is used to fill the unit circle.
You have to add multiplication coefficient A
rad := A * Sqrt(i) / Sqrt(N);
Last hole with index N should be inside radO:
A * Sqrt(N) / Sqrt(N) < radO - HoleRadius
so
A = radO - HoleRadius;
Now for the first hole:
A * Sqrt(N - holeQTY + 1) / Sqrt(N) > (radI + HoleRadius)
N - holeQTY + 1 > N * Sqr((radI + HoleRadius) / A)
N * (1 - Sqr((radI + HoleRadius) / A)) > holeQTY - 1
N = Ceil((holeQTY - 1) / (1 - Sqr((radI + HoleRadius) / A))
Result:
You have to draw holeQTY points with indexes (N - holeQTY + 1)..N using coefficient A. Sample code:
var
A, golden_angle, radI, radO, rad, theta, MinGap: Double;
N, x, y, i, holeQTY, holeRadius: Integer;
begin
holeQTY := 120;
radI := 50;
radO := 200;
holeRadius := 4;
MinGap := 3;
golden_angle := Pi * (3 - Sqrt(5));
A := Floor(radO - (holeRadius + MinGap));
N := Ceil((holeQTY - 1) / (1 - Sqr((radI + holeRadius + MinGap) / A)));
Canvas.Ellipse(300 - 200, 300 - 200, 300 + 201, 300 + 201);
Canvas.Ellipse(300 - 50, 300 - 50, 300 + 51, 300 + 51);
for i := (N - holeQTY + 1) to N do begin
theta := i * golden_angle;
rad := A * Sqrt(i) / Sqrt(N);
x := 300 + Round(rad * Cos(theta));
y := 300 + Round(rad * Sin(theta));
Canvas.Ellipse(x - holeRadius, y - holeRadius, x + holeRadius + 1, y + holeRadius + 1);
end;
(picture made before MinGap introduction)
Code with double values
holeQTY := 17;
radI := 0.1234;
radO := 0.23456;
holeRadius := 0.00635;
MinGap := 0.0000635;
golden_angle := Pi * (3 - Sqrt(5));
A := radO - (holeRadius + MinGap);
N := Ceil((holeQTY - 1) / (1 - Sqr((radI + holeRadius + MinGap) / A)));
for i := (N - holeQTY + 1) to N do begin
theta := i * golden_angle;
rad := A * Sqrt(i) / Sqrt(N);
x := rad * Cos(theta);
y := rad * Sin(theta);
Memo1.Lines.Add(Format('r:%5.4f x:%f y:%f', [rad, x, y]));
end;
produces output
r:0.1317 x:0.12 y:0.05
r:0.1397 x:-0.13 y:0.05
r:0.1473 x:0.06 y:-0.13
r:0.1545 x:0.05 y:0.15
r:0.1613 x:-0.14 y:-0.08
r:0.1679 x:0.16 y:-0.04
r:0.1742 x:-0.10 y:0.14
r:0.1804 x:-0.02 y:-0.18
r:0.1863 x:0.14 y:0.12
r:0.1920 x:-0.19 y:0.01
r:0.1976 x:0.14 y:-0.14
r:0.2030 x:-0.01 y:0.20
r:0.2083 x:-0.13 y:-0.16
r:0.2134 x:0.21 y:0.03
r:0.2184 x:-0.18 y:0.12
r:0.2233 x:0.05 y:-0.22
r:0.2281 x:0.11 y:0.20
And finally possible function separated from GUI
type
TPointDouble = record
X, Y: Double;
end;
function CalcRingPoints(const
InnerRadius,
OuterRadius,
HoleRadius,
CoverageRatio //range 0..1
: Double)
: TArray<TPointDouble>;
//doesn't check input validity and possible hole overlaps!
var
ACoeff, golden_angle, rad, theta, MinGap, Area: Double;
N, i, j, holeQTY: Integer;
begin
holeQTY := Round(CoverageRatio * (Sqr(OuterRadius) - Sqr(InnerRadius)) /
Sqr(HoleRadius));
MinGap := 0.1 * HoleRadius;
golden_angle := Pi * (3 - Sqrt(5));
ACoeff := OuterRadius - (HoleRadius + MinGap);
N := Ceil((holeQTY - 1) / (1 - Sqr((InnerRadius + HoleRadius + MinGap) /
ACoeff)));
SetLength(Result, holeQTY);
for i := (N - holeQTY + 1) to N do begin
theta := i * golden_angle;
rad := ACoeff * Sqrt(i / N);
j := i - (N - holeQTY + 1);
Result[j].X := rad * Cos(theta);
Result[j].Y := rad * Sin(theta);
end;
end;
and it's usage
var
Pts: TArray<TPointDouble>;
i: Integer;
begin
Pts := CalcRingPoints(1, 2, 0.2, 0.5);
for i := 0 to High(Pts) do
Memo1.Lines.Add(Format('%d r:%5.4f x:%f y:%f',
[i, Hypot(Pts[i].X, Pts[i].Y), Pts[i].X, Pts[i].Y]));
//37 points
Let's see.
The formula for a solid disk is obvious, right?
r[max] = r_coeff * sqrt(max) which gives us r_coeff = r[max]/sqrt[max];
Your problem seems to be about what to do when our plane is not the solid disk but a subtraction of a smaller disk from the larger one.
Well, the dumb lazy approach would be to correct the numbers of holes. Since the square of a disk is proportional to the radius squared, and since we want our holes by dispersed mostly equidistant we can hope that as we have many enough holes the share (percentage) of the holes is proportional to the disc square, right ?
So we have
N = 10 190 = N_outer_disk - N_inner_disk
N_outer_disk / N_inner_disk ≈ S_outer / S_inner = (R_outer / R_inner)^2
N_inner_disk / N_outer_disk ≈ S_inner / S_outer = (R_inner / R_outer)^2
And we have to run the loop I := N_inner_disk + 1 to N_outer_disk calculating
r[I] = r_coeff * SqRt(I)
angle[I] = I * 137.508 * degrees-to-radians-coefficient
Or perhaps we better run the loop from N_inner_disk - 10% to N_outer_disk + 10% and just filter all the exceedingly large output by (hole_radius + 10%) + r_hole[I] < R_outer and r_hole[I] > R_inner + (hole_radius + 10%) - since our calculations are approximate we would anyway have to employ the filtering, and then we can just use it from starters.
I believe you need one more variable to add - the minimal gap between the hole edge and the edges of inner/outer circles, so that hoels would not be "drlled" on the edge partially out of the plat. That gap for the lack of said variable and for mere simplicity I set above to be 10% of the hole radius, which is not exactly correct
Coming back to our N-s...
N = 10 190 ≈ N_outer_disk - N_outer_disk*(R_inner / R_outer)^2 = N_outer_disk * (1 - (R_inner / R_outer)^2)
and
N = 10 190 ≈ N_inner_disk*(R_outer / R_inner)^2 - N_inner_disk = N_inner_disk * ((R_outer / R_inner)^2 - 1)
Thus for given values of N, R_outer, R_inner
N_inner_disk ≈ N / ((R_outer / R_inner)^2 - 1)
N_outer_disk ≈ N / (1 - (R_inner / R_outer)^2)
Now run your loop from N_inner+1 to N_outer, check the total amount of holes passed through the geometrical filter aforementioned and their accumulated square, and if needed adjust the coefficients slightly to come to slightly better N_inner and N_outer until you would approach the distribution good enough to suit you.
Consequently the r_coeff = r[max]/sqrt[max] would turn into r_coeff ≈ R_inner/sqrt[N_inner] ≈ R_outer/sqrt[N_outer]
Checking that the coefficients calculated with those two methods are very close to each other would be another check that the calculations of two N-s themselves were internally consistent.

Image "fade" in / out (Not opacity)

I want to "fade" in / out an image. But not opacity-wise.
Pictures say more than words:
Original image:
Desired image:
How can I do that programatically? Not the way like "use Bitmap.Canvas" but the mathematical approach. ("For dummies" if possible ... :D)
I want the image to have a fade-in / -out area, not linear increasing but "curvy". I guess it has something to do with Bezier curves? If yes, how would I setup the points to get a curve like that?
Or what would be your approach here?
Thanks for any help! :)
Here is roughly how you would go about doing it (as you said, you're looking for the logic and not the full implementation)
Create the basic shape outline: Create a partial sine-wave, such that the semi-period (half-wavelength: P1 = L/2) equals the length (x-coordinate size) of your image.
Add Overtones: Add to it another sine-function. This time with wavelength given by P2 = P1 / 2 + rnd where rnd is a random real number in the interval (-P1/4 , +P1 / 4)
Repeat: Now P2 becomes the new P1.
That way you can generate the 'wavy-waves' by modulating the main wave and you will get the top boundary.
You can change the sign and get the lower boundary.
The word you might be looking for (for the shape, i.e.) is Overtones. You could look up more on generating overtones for optics or acoustics.
This example for adding overtones to a straight line would give a better idea. The code above adds up these sinusoidal waves of randomly shortening periods to create the wave-on-wave effect (source)
Thanks again, hnk. You got me the right ideas. :)
I did a little work and ended up with this:
procedure GenerateOverlayEdges(ABitmap: TBitmap; ARadiusX, ARadiusY: Integer);
const
MAX_ANGLE = 90;
var
ShapePosition, ShapeIndex, ShapesCount,
Angle, X, Y, RadiusX, RadiusY, CenterY: Integer;
PolyPoints: Array of TPoint;
begin
ABitmap.Canvas.Pen.Color := clBlack;
ABitmap.Canvas.Brush.Color := clBlack;
//ABitmap.Canvas.Pen.Color := clWhite;
//ABitmap.Canvas.Brush.Color := clWhite;
RadiusX := ARadiusX;
RadiusY := ARadiusY;
CenterY := Round(ABitmap.Height / 2);
ShapesCount := Ceil(CenterY / RadiusY);
for ShapePosition := 1 to 4 do // 1 = TopLeft, 2 = BottomLeft, 3 = TopRight, 4 = BottomRight
for ShapeIndex := 0 to ShapesCount - 1 do // Index of current "stair"
begin
Finalize(PolyPoints);
SetLength(PolyPoints, MAX_ANGLE + 1);
for Angle := 0 to MAX_ANGLE do
begin
case ShapePosition of
1, 3:
begin
RadiusX := Abs(RadiusX);
RadiusY := Abs(RadiusY);
end;
2, 4:
begin
RadiusX := Abs(RadiusX);
RadiusY := -Abs(RadiusY);
end;
end;
X := Trunc(RadiusX * Cos(Angle * 2 * Pi / 360));
Y := Trunc(RadiusY * Sin(Angle * 2 * Pi / 360));
case ShapePosition of
1:
begin
X := X + ShapeIndex * RadiusX;
Y := Y + CenterY - RadiusY - ShapeIndex * RadiusY;
end;
2:
begin
X := X + ShapeIndex * RadiusX;
Y := Y + CenterY - RadiusY + ShapeIndex * Abs(RadiusY);
end;
3:
begin
X := ABitmap.Width - X - ShapeIndex * Abs(RadiusX);
Y := Y + CenterY - RadiusY - ShapeIndex * RadiusY;
end;
4:
begin
X := ABitmap.Width - X - ShapeIndex * Abs(RadiusX);
Y := Y + CenterY + Abs(RadiusY) + ShapeIndex * Abs(RadiusY);
end;
end;
// Add points as part of a Polyon
PolyPoints[Angle] := Point(X, Y);
end;
// Set Y to the Y borders for the very first and last point of the polygon so we will get a "closed" shape
case ShapePosition of
1, 3:
begin
PolyPoints[0] := Point(PolyPoints[0].X, 0);
PolyPoints[Angle - 1] := Point(PolyPoints[Angle - 1].X, 0);
end;
2, 4:
begin
PolyPoints[0] := Point(PolyPoints[0].X, ABitmap.Height);
PolyPoints[Angle - 1] := Point(PolyPoints[Angle - 1].X, ABitmap.Height);
end;
end;
// Draw the poly points ... and fill the background at the same time
ABitmap.Canvas.Polygon(PolyPoints);
end;
Finalize(PolyPoints);
end;
Usage:
var
Bmp: TBitmap;
begin
Bmp := TBitmap.Create;
try
Bmp.LoadFromFile('C:\Temp\Osc 2.bmp'); // Original Oscilloscope Image
GenerateOverlayEdges(Bmp, 15, 20);
Bmp.SaveToFile('C:\Temp\Osc 3.bmp');
finally
Bmp.Free;
end;
end;
By changing the RadiusX and RadiusY parameters of the GenerateOverlayEdges function I can adjust the results:
8x8:
15x20:
20x10:

Shape component rotation

I need to rotate one Shape component (ellipse) around the other one (circle). It's seems to me that it's better to do it with polar coordinates. So the rotation formula is:
X := Round(CenterX + SIN(Angle) * Radius);
Y := Round(CenterY + COS(Angle) * Radius);
where X, Y - ellipse coordinates, Radius - rotation radius; Angle is rotation angle;
CenterX, CenterY - center of rotation.
Also I got that in Timer component I must write the following code:
Angle := Angle + 0.01;
if Angle> 2*Pi then Angle := Angle - 2*Pi;
And Shape must be redrawn.
It would look like this:
But I can't gather all in a bunch. I don't know how to organize this all. Thanx for any help.
Add a variable t: double to your form class, and do
procedure TForm1.Timer1Timer(Sender: TObject);
var
cx, cy: integer;
x, y: integer;
const
r = 200;
begin
cx := Shape1.Left + Shape1.Width div 2;
cy := Shape1.Top + Shape1.Height div 2;
x := cx + round(r*sin(t));
y := cy + round(r*cos(t));
Shape2.Left := x - Shape2.Width div 2;
Shape2.Top := y - Shape2.Height div 2;
t := t + 0.01;
end;
where Timer1.Interval = 30, say.
Personally, however, I really dislike when people do animations by moving VCL controls around. It is much better to resort to manual GDI (or even OpenGL) drawing.

Resources