I have a line identify by x1,y1,x2,y2 which are double values. Then I have
several graphical objects (Let's name the class TShape) which cordinates
are Left, Top, Right, Bottom: double. Only Top and Left properties are writable
value. When dragging the TShape around the top and left values are updated.
I am using a function to discovery when TShape is near a Line. The function
definition is:
function NearLine(const Target: TPoint; X1, Y1, X2, Y2: double; Off: integer = 5): boolean;
NearLine returns true if point specified by Target is near the line specified by Point1
and Point2. The point must be at the distance specified by Off.
I use the function with Off = 0;
In my implementation Target is the center of the TShape which I keep updated
calculating it from Top and Left properties. Because Target
is TPoint I do:
1-
CPoint.X := Trunc(Center.X);
CPoint.Y := Trunc(Center.Y);
2-
and when the function NearLine above is true I force the mouse to release with:
3-
Mouse_Event(MOUSEEVENTF_ABSOLUTE or MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
1,2 an 3 are called within an event UpdateMove which is called while
dragging the shape.
This allow me to "stick" the TShape almost near the line however it's not
exactly what I want to achive... obviously there is an error due the Trunc function.
The error is:
deltaX := Frac(Center.X);
deltaY := Frac(Center.Y);
After releasing the mouse programmatically how I can force all the center
therefore all the shapes to be perfectly lined (collinear) with the
line?
Any help? :(
You're asking the question wrong, that's why you cant' see the answer your self. If 3 points aren't collinear, you're not going to "force" them collinear unless you change the laws of math and/or physics.
What you probably want is to find a point on the line defined by two points that's closest to your point of reference. That's pretty simple geometry: The closest point is as at the "foot" of a perpendicular drawn from your third point to the line defined by the first two! You can solve that using the Pythagoran theory alone, you don't even need fancy analytic geometry.
Related
I draw a path consisting of 2 lines going up and then back down to the same spot, or almost the same spot, but the first line is drawn too high. If I then draw the same lines using DrawLine I don't see the issue. Why is this happening?
Below is an example. Just drop a 400x400 TImage on a blank multiplatform form. The code draws 2 red paths, one with close to a 180 degree angle between the lines and one with less of an angle. The same lines are then drawn using DrawLine in blue. If the DrawPath function works correctly then the blue lines should completely cover the red lines, but they don't. In this example with a scale of 1.5 the path extends 7 pixels too high for the first path. The extent of the error reduces as the lines get further apart. The issue still happens with a scale of 1, but is less obvious.
procedure TForm1.FormActivate(Sender: TObject);
var
LPath1, LPath2 : TPathData;
i : Integer;
begin
// A path of 2 lines going up and then back down to almost the same spot
LPath1 := TPathData.Create;
LPath1.MoveTo(PointF(100,200));
LPath1.LineTo(PointF(100,50 ));
LPath1.LineTo(PointF(105,200));
// A path of 2 lines going up and then back down at a wider angle
LPath2 := TPathData.Create;
LPath2.MoveTo(PointF(200,200));
LPath2.LineTo(PointF(200,50 ));
LPath2.LineTo(PointF(260,200));
Image1.Bitmap.BitmapScale := 1.5; // The issue shows up more at larger scales
Image1.Bitmap.SetSize(Trunc(Image1.Width), Trunc(Image1.Height));
with Image1.Bitmap.Canvas do if BeginScene then begin
Clear(TAlphaColorRec.White);
// Draw the paths using DrawPath in red
Stroke.Color := TAlphaColorRec.Red;
Stroke.Thickness := 1;
DrawPath(LPath1, 1);
DrawPath(LPath2, 1);
// Draw the paths using DrawLine in blue over the top
// The red lines should be completely hidden under the blue
Stroke.Color := TAlphaColorRec.Blue;
for i := 1 to LPath1.Count - 1 do
DrawLine(LPath1.Points[i-1].Point, LPath1.Points[i].Point, 1);
for i := 1 to LPath2.Count - 1 do
DrawLine(LPath2.Points[i-1].Point, LPath2.Points[i].Point, 1);
EndScene;
end;
LPath1.Free;
LPath2.Free;
Image1.Bitmap.SaveToFile('test.png');
end;
Result of the code when run in Windows 10. I'm using Delphi 11, but the same issue happens with Delphi 10. I've tried switching GPU but the same issue occurs.
Enlarged view:
I've come to the conclusion that this isn't a glitch at all. It's because the default setting of TCanvas.Stroke.Join is TStrokeJoin.Miter. The artefact seen is just the sharp spike of the mitred corner. Using MoveTo before each line segment when constructing the path does solve the issue (because there's no join between the separate line segments) but so does setting the TCanvas.Stroke.Join parameter to TStrokeJoin.Round or TStrokeJoin.Bevel.
Note that at very sharp angles approaching 180 degrees, the miter join would become infinite. However, it appears to be limited somehow, perhaps in proportion to the stroke thickness. I don't think there's a way to change this miter limit in delphi.
This is because by default TPath is making smooth transitions between different path segments. I'm guessing it might be using Quadratic interpolation for making these smooth transitions.
Yes making smooth transition between two lines doesn't seem logical but it looks this is how it is implemented.
Now you can avoid this by telling the TPath that your two lines are not connected and thus should be treated as two separate lines even thou in reality they are connected. And you can do this by simply calling Path.MoveTo which is intended to shift position so you can create another unconnected line that dos not continue from your last path point.
Here is how modified code for your first sharp cornered line would look like:
NOTE that I'm specifying the exact same position for MoveTo command that was used for rendering of previous path line since you don't want the new line to start at new position.
// A path of 2 lines going up and then back down to almost the same spot
LPath1 := TPathData.Create;
LPath1.MoveTo(PointF(100,200));
LPath1.LineTo(PointF(100,50 ));
//Add move to command to prevent Path from trying to make smooth transitions between two lines
LPath1.MoveTo(PointF(100,50));
LPath1.LineTo(PointF(105,200));
I'm using basic TeeChart version arrived with Delphi 10.1.
A double click on a LineSeries toggle the point and the Mark of this series.
I'm also present the cursor value.
However, there is a shift between the two, they are not identical.
procedure TfrmMain.SeriesDblClick(Sender: TChartSeries; ValueIndex: Integer;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
tmpX1,tmpY1,tmpX2,tmpY2:Double;
begin
// First values
tmpX1:=Chart.Axes.Bottom.CalcPosPoint(X);
tmpY1:=Chart.Axes.Left.CalcPosPoint(Y);
// The prevous values are not identical to:
tmpX2:=(Sender as tLineSeries).XValue[ValueIndex];
tmpY2:=(Sender as tLineSeries).YValue[ValueIndex];
end;
How to fix it?
It's not going to be possible to resolve this. Information is lost when you map from the real-valued space to integer screen space, and back again.
CalcPosPoint takes the integer screen coordinate and maps that to the real-valued axis space. On the other hand XValue[] and YValue[] return the original data.
The chart code starts with the real values in XValue[] and YValue[]. These are mapped to integer values. That mapping by necessity rounds to the nearest integer, after the transformation to real axis values. At that point information is lost, that cannot be retrieved.
As a thought experiment, consider two real X values that were very close. Let's suppose that XValue[0] = 0.0 and XValue[1] = 1e-6, for the sake of argument. The X axis ranges from 0 to 1 say. Now, in this scenario, you would need 1e6 pixels on the screen in order for these two points to have different integer coordinates when mapped onto the screen. Your screen doesn't have that many pixels and so these two points, with different real X values, map to the same X coordinate on the screen.
Hopefully this example shows the imposibility of what you are trying to do. Perhaps the best you can achieve is to search the raw coordinates in XValue[] and YValue[] to find the closes to (tmpX1, tmpY1), and report those closest raw coordinate values.
I have a problem to use this bulge arc (dxf parser) function in C++ getArcDataFromBulge().
https://github.com/Embroidermodder/Embroidermodder/blob/master/libembroidery/geom-arc.c
I have my drawArc() function which need 'start angle' and 'sweep angle' parameters from this getArcDataFromBulge() function.
My drawArc() function use OpenGL 2D coordinate system with right side zero angle position and when I get values from getArcDataFromBulge() and recalculate it (0+-, 180+-, 360+-) I have something like unexpected opposite angles as results. It looks like clockwise-counterclockwise problem, but I'm think is not, I'm not sure. Do you have some idea what is going on?
For example:
tempBulge.bulge := 0.70;
arcMidAngle := RadToDeg( atan2(tempBulge.arcMidY - tempBulge.arcCenterY,
tempBulge.arcCenterX - tempBulge.arcMidX) );
After calculaton: arcMidAngle = 179.999
When I add and subtract from this point half of arc chord angle, I get start and end angles of my arc: 90°, 270° but it's not the same arc when I open dxf with some CAD software, it is opposite than origin drawing.
If you have an arc from 0° to 90°, it could be a 1/4 circle or a 3/4 circle.
You need to parse the $ANGDIR and $ANGBASE variables from the HEADER section which tells you in which direction angles are defined ($ANGDIR) and where the 0° angle starts ($ANGBASE) within that specific DXF file:
Variable Group code Description
$ANGBASE 50 Angle 0 direction
$ANGDIR 70 1 = Clockwise angles, 0 = Counterclockwise
For DXF, if $ANGBASE = 0, then 0° is on the right of the center, alike Windows.
Furthermore, in DXF, the positive Y-axis is upwards, in contrast to many Windows API's where the positive Y-axis is downwards.
With this code I want to draw a rectangle:
procedure TForm1.Button1Click(Sender: TObject);
var rectangle:Trect;
begin
fx:=400;
fy:=400;
sc1:=base/fx;
sc2:=altezza/fy;
sc:=max(sc1, sc2);
lx:=fx*sc;
ly:=fy*sc;
xc:=base/2;
yc:=altezza/2;
x1:=xc-(lx/2); x2:=xc+(lx/2); y1:=yc-(ly/2); y2:=yc+(ly/2);
panel1.Repaint;
panel1.Canvas.Brush.color:= clblack;
panel1.Canvas.line((panel1.width div 2),0,(panel1.Width div 2), panel1.Height);
panel1.Canvas.line(0,(panel1.height div 2), panel1.Width,(panel1.Height div 2));
panel1.canvas.brush.style:=bsclear;
Rectangle:=rect(x1, y1, x2, y2);
end;
But there is a problem because I have to use only integer values.
Is it possible to use real values for drawing a rectangle with TCanvas?
The simple answer is no. Graphic devices as represented by TCanvas use a coordinate system with integral coordinates. If your coordinates are real values then you need to use some form of mapping between your coordinate system and the integral device coordinates.
However, in this instance it looks like it's not that complex. You don't need real valued coordinates per se. You only have real values because you used real division. Perhaps all you need to do is use integer division, div, rather than real division. Or perhaps you would prefer Round.
A bigger problem is that your code is in the wrong place. You cannot paint in a button handler. Windows will not remember what you painted. The next time the window is invalidated it will ask the panel to refresh itself, and your rectangle will be gone. Painting code needs to be inside an overriden Paint method or equivalent. Perhaps you need a paint box control.
I am trying to convert some Delphi code as we are re-writing a Delphi 6.0 (VCL) application in .Net. I am not sure and could not figure out the comparison between 2 Delphi Tpoints(x,y) with that of C# Point(x,y).
I am trying to draw a line between 2 points but since I have no idea how Delphi draws it, I am not able to set the C# coordinates for it.
The Delphi code is simple:
Canvas.MoveTo(x, y - 128);
Canvas.LineTo(x, y);
I know about the C# coordinates though about 72 Points per inch and need to calculate the pixel density. But I am not sure about the Delphi PPI.
Any would be appreciated. Thanks.
Edit: If someone is wondering what TPoint I am talking when there is none in my code snippet, Canvas.MoveTo sets the PenPos property of the canvas which is of type TPoint.
I'm not sure what the exact question is that's being asked here. You have no Delphi TPoint in your code snippet; you simply have client rect logical coordinates.
The origin is at X = 0, Y = 0, which is the top left corner of the client area. Increasing X moves the position to the right, and increasing Y moves the position down. Logical units are pixels, so starting at the origin of 0, 0, a Canvas.MoveTo(10, 10) would set the new drawing position in from the left edge 10 pixels and down from the top 10 pixels, and a Canvas.LineTo(20, 20) from there would draw a line from the point at 10, 10 to 20, 20.
TCanvas.MoveTo and TCanvas.LineTo are simply wrappers around the underlying Windows GDI functions MoveToEx (with an always NULL third parameter) and LineTo.
As far as the C# equivalent, if you're referring to System.Drawing.Point, the units used are exactly the same (although I'm not sure where the origin is based by default). Given an origin of 0, 0, System.Drawing.Point(10, 10) should be the same position described above - 10 pixels from the left edge and 10 pixels down from the top edge.
A quick check confirms that the origin in a WinForms application is in fact the top left corner of the client area, using:
// Delphi code
procedure TForm3.FormPaint(Sender: TObject);
begin
Canvas.Pen.Color := clRed;
Canvas.MoveTo(0, 0);
Canvas.LineTo(100, 100);
end;
// C# code
private void Form1_Paint(object sender, PaintEventArgs e)
{
Pen newPen = new System.Drawing.Pen(Color.Red);
e.Graphics.DrawLine(newPen, new Point(0, 0), new Point(100, 100));
}
This produces the following output: