Find a tangent between line segment and circle - javascript

I have a line segment A (x1, y1), B (x2, y2) and a circle with the center C (x3, y3). Also I have radius of the circle. How do I get tangents?
These tangent lines should always be parallel to line segment.
P.S. Sorry if it is doesn't make sense to you, I am very very bad at math. Ask me any questions, I just don't know what else be needed to solve this task. Thanks.

Segment defines direction vector
D = (dx, dy) = (b.x - ax, b.y - a.y)
dlen = Length(D) = Sqrt(dx * dx + dy * dy)
Radius-vector is perpendicular to tangent and it's length is R. Let circle center is (0,0) (we will make correction after). So
x * x + y * y = R^2
x * dx + y * dy = 0 //scalar product
Solving this system for unknown x,y, we have
x = +- R * dy / dlen
y = -+ R * dx / dlen
And after correction two base points of tangents are
px1 = x3 + R * dy / dlen
py1 = y3 - R * dx / dlen
px2 = x3 - R * dy / dlen
py2 = y3 + R * dx / dlen
Direction vectors for these tangents are D, so you can define second points for tangents as
px1_2 = x3 + R * dy / dlen + dx
py1_2 = y3 - R * dx / dlen + dy
And the first tangent is line through (px1, py1) and (px1_2, py1_2)
Note that solution works for any direction of segment (including horizontal and vertical)

Write the equation of the line AB in the implicit form
a X + b Y + c = 0
where the coefficients are normalized so that a² + b² = 1. https://en.wikipedia.org/wiki/Linear_equation#Two-point_form
The two requested tangents are parallel to AB, with equation
a X + b Y + c' = 0
and it suffices to express that the distance of the lines to the center of the circle is R,
|a Xc + b Yc + c'| = R
giving two solutions in c'.

I assume you are looking for the tangent lines with slope == the slope of the line. I will detail the algorithm and I leave the actual code to you.
So first lineslope = (y2-y1)/(x2-x1).
Next a circle with radius r centered at (x3, y3) can be described by the parametric equations x = r*cos(t) + x3; y = r*sin(t) + y3.
The slope of the circle at a given t is (dy/dt)/(dx/dt) = -cos(t)/sin(t) = -1/tan(t).
Set -1/tan(t) = lineslope = (y2-y1)/(x2-x1). Solving for t yields t = (pi / 2) - arctan(-(y2-y1)/(x2-x1)). (This should produce two values for t on [0, 2pi] which will be the two places the slope of the tangent line equals the slope of your segment).
Plug the value for t you just calculated into the circle equations from (3), and you will have the point (x4, y4) on the circle where the tangent line has the same slope as your line segment.
The equation of the tangent line is then just y - y4 = lineslope*(x-x4)

Related

Finding two points that intersect a rectangle on a line that is perpendicular to a line segment

I'm having the following problem:
Given:
A rectangle with a defined height(Y) and width(X)
The line segment given by the points A and B
A point inside the segment C
Find the points D and E that:
Intersect the rectangle
Forms a line segment that goes through C
Forms a line segment that is perpendicular to the segment AB
To solve this problem, I've tried first calculating the slope and creating a line function, but all answers that I've seen to get the intersection between a line and a polygon uses a line segment and not a line function. How can I solve this? Am I missing a better way to find a perpendicular line that doesn't require a function?
function getPerpendicular(ax,ay,bx,by,cx,cy,x,y){
let a=bx-ax;
let b=by-ay;
let slope;
let line;
// Because if a==0 the slope is infinite
if(a===0){
line=function(y){
return cx;
}
}else{
slope= (b)/(-a);
line=function(x){
return slope*x+cy-cx;
}
}
// Intersection with the line function?
}
Get direction vector for AB line (your a, b)
xx=bx-ax;
yy=by-ay;
Get perpendicular vector
dx = - yy
dy = xx
Now perpendicular line has parametric equation (it is for reference, no need to implement)
x = cx + dx * t
y = cy + dy * t
first check for horizontal/vertical lines
if dx = 0 then
return cx, 0, cx, Y
if dy = 0 then
return 0, cy, X, cy
prerequisites: potential border positions
if dx > 0 then
bx1 = X
bx2 = 0
else
bx1 = 0
bx2 = X
if dy > 0 then
by1 = Y
by2 = 0
else
by1 = 0
by2 = Y
in general case find parameters of intersection with horizontal and vertical edge
tx1 = (bx1 - cx) / dx
ty1 = (by1 - cy) / dy
tx2 = (bx2 - cx) / dx //should be negative
ty2 = (by2 - cy) / dy //should be negative
and get intersection for smaller parameter value for one end:
if tx1 <= ty1 then
ix1 = bx1
iy1 = cy + tx1 * dy
else
iy1 = by1
ix1 = cx + ty1 * dx
and for larger parameter value at another end:
if tx2 >= ty2 then
ix2 = bx2
iy2 = cy + tx2 * dy
else
iy2 = by2
ix2 = cx + ty2 * dx
now return both intersection points
return ix1, iy1, ix2, iy2

Draw ellipse with 5points in canvas

I am not so good in Mathematics. I have a requirement to draw an ellipse using 5 coordinates where user will click on 5 different position in a canvas and getting that clicked coordinate 1 ellipse will drawn. To draw ellipse in canvas I have the method
ctx.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle [, anticlockwise]);
Where I need the center position and 2radius of the ellipse. I only have 5coordinates of the perimeter of the ellipse. I get a matrics formula to calculate the ellipse.
ax2+bxy+cy2+dx+ey+f=0
I am unable to convert that equation to js. I am thankful to you if you please help me out to calculate the mazor and minor radius and center point of ellipse from 5 arbitary points
Having 5 points, you can find general formula of conic section (here ellipse) expanding determinant of this matrix (substitute xi,yi with your point coordinates):
(picture taken here)
Simple example to begin with
Using my answer for inverse problem
//calc implicit ellipse equation
//semiaxes rx, ry; rotated at fi radians; centered at (cx,cy)
//note: implicit form Ax^2+Bxy+Cy^2+Dx+Ey+F=0 (not 2B,2D,2E)
// in Pascal notation Sqr is squared
B := Sin(2 * Fi) * (ry * ry - rx * rx);
A := Sqr(ry * Cos(fi)) + Sqr(rx * Sin(fi));
C := Sqr(rx * Cos(fi)) + Sqr(ry * Sin(fi));
D := -B * cy - 2 * A * cx;
E := -2 * C * cy - B * cx;
F := C * cy * cy + A * cx * cx + B * cx * cy - rx * rx * ry * ry;
we can see that
Fi = 0.5 * atan2(B, A-C)
then
ry^2+rx^2 = A + C
ry^2-rx^2 = B / Sin(2*Fi)
so
ry = Sqrt((A + C + B / Sin(2*Fi))/2)
rx = Sqrt((A + C - B / Sin(2*Fi))/2)
(except for Fi=0 case, where we can extract semiaxes from A and C directly)
and then find cx, cy from D/E equation system
Also look at Wiki formulas for the same problem

Bezier curve math

3 weeks ago I asked a question on how to keep the ratio for the bezier curve when changing the X points. "MBo" helps me, but there was a problem and he recommend me to make a new topic.
The problem is that P0.Y and P2.Y can be different and therefore the curve looks like a "brolly".
Now I have this and when changing P0.X and P2.X I want keep the ratio which work's fine:
https://www.w3schools.com/code/tryit.asp?filename=FXDIZMBCCYNA
When changing P0.Y for example it looks like a "brolly" (the P1.X is not exactly in the middle):
https://www.w3schools.com/code/tryit.asp?filename=FXDJ733KQZM4
OK, I try to explain it more closely.
I have four points (X1, Y1, X2, Y2) and want a bezier curve based on the points so:
P0.X is on X1, P1.X between X1 and X2, and P2.X on X2.
P0.Y is on Y1 and P2.Y on Y2.
When I now have this:
ctx.moveTo(0, 50);
ctx.quadraticCurveTo(100, 25, 200, 50);
And change the position of x1 and x2 I keep the ratio from above:
ctx.moveTo(0, 50);
ctx.quadraticCurveTo(25, 44, 50, 50);
Ok, so far this part work's fine. Now my problem is when I change the Y1 or Y2 it looks like "brolly" also the curve is not round like above because the P1.X is not exactly in the middle.
ctx.moveTo(0, 250);
ctx.quadraticCurveTo(100, 25, 200, 50);
Where it should like this:
I see it like this:
So you got some 3 points (P0(x0,y0),P1(x1,y1),P2(x2,y2)). Now what you want to do is still unclear but I assume you just want to resize the curve along x axis and maintain the shape in y axis too...
So you need to change all the stuff proprtionaly... so maintaining this:
(x1-x0) / (x2-x0) = (x1'-x0') / (x2'-x0')
(y1-y0) / (y2-y0) = (y1'-y0') / (y2'-y0')
(x1-x0) / (x1'-x0') = c
(y1-y0) / (y1'-y0') = c
(x2-x0) / (x2'-x0') = c
(y2-y0) / (y2'-y0') = c
(x2-x1) / (x2'-x1') = c
(y2-y1) / (y2'-y1') = c
where x,y are original points and x',y' are the changed ones
for your example:
P0=( 0,50)
P1=(100,25)
P2=(200,50)
x2'=50
you need to recompute the rest start with scale:
c = (x2-x0) / (x2'-x0') = (200-0)/(50-0) = 200/50 = 4
then just recompute what is missing:
(x1-x0) / c = (x1'-x0') // x0=0, x0'=0
x1 / c = x1'
x1' = 100/4 = 25
(y1'-y0') = (y1-y0) / c // y0' = y0
y1' = (y1-y0) / c + y0
y1' = (25-50) / 4 + 50
y1' = 43.75
(y2'-y0') = (y2-y0) / c // y0' = y0
y1' = (y2-y0) / c + y0
y1' = (50-50) / 4 + 50
y1' = 50
The same goes for changing y ... once you change any y you need to recompute the remaining x,y affected in the same manner...
When Bezier curve undergoes some affine transformation, the same transform is applied to their control points.
In your case transform is rotation and scaling around the first point (P0) of the curve.
Rotation angle is
fi = arctan((P2'.Y - P2.Y) / (P2.X - P0.X))
Scaling coeficient
Cf = Sqrt(1 + (P2'.Y - P2.Y)^2/(P2.X - P0.X)^2)
So new coordintes for control points are
xx = P1.X - P0.X
yy = P1.Y - P0.Y
nx = xx * Cos(fi) - yy * Sin(fi)
ny = xx * Sin(fi) + yy * Cos(fi)
P1'.X = P0.X + nx * Cf
P1'.Y = P0.Y + ny * Cf

Calculate the control points for cubic Bézier curve from an arc

I have an geometric arc defined by the two end points (P0 = x1,y1 and P4 = x2,y2) and either the radius R or the center of the arc (C = xc, yc). I need to calculate the two control points P2, P3 for the cubic Bézier curve in javascript. In my particular case the arc angle will be less than 90 degrees.
I have search the internet and stackoverflow and any solutions are incomplete, not generalized for an arc of indeterminate angle, or too complex for me to understand.
Does anyone have any javascript code or pseudo code that would help? I previously asked a similar question but improperly referred to the Bézier curve as quadratic when I need a cubic Bézier curve.
Cubic Bezier approximation of the circle arc, defined by coordinates of starting and ending points, center and radius of the circle – (x1,y1) = P0, (x2,y2) = P3, C = (cx,cy), R.
(Approximation of arс, defined by angles, could be found here)
Control points of Bezier should be on the tangents to given points on the circle. One possible approximation method – middle point of (symmetric) curve should lay on the circle.
(Note that for good approximation arc angle cannot be large)
Radius-vectors of given points:
V1 = (x1-cx, y1 - cy)
V2 = (x2-cx, y2 - cy)
Tangent vectors:
T1 = (cy – y1, x1 – cx)
T2 = (y2 - cy, cx – x2)
Coordinates of control points (k – unknown yet factor):
P1 = P0 + k * T1
P2 = P3 + k * T2
Middle point of Bezier:
MB = B(1/2) = P0 * 1/8 + P1 * 3/8 + P2 * 3/8 + P3 * 1/8 =
P0 * 1/8 + P0 * 3/8 + k * T1 * 3/8 + P3 * 3/8 + k * T2 * 3/8 + P3 * 1/8 =
(P0 + P3)/2 + k * 3/8 * (T1 +T2)
Now solve equation against k factor
(MB.X – cx)^2 + (MB.Y – cy)^2 = R^2
There are two solutions possible – we need positive one, if input points are in the right order (in common case for arcs < Pi - with the smallest magnitude)
Delphi code (doesn't check input data, doesn't treat extra cases) and it's result:
procedure BezierArcByPoints(x1, y1, x2, y2, cx, cy, R: Integer;
var Pts: array of TPoint);
var
t1x, t1y, t2x, t2y, dx, dy, k, tx, ty, D, a, b, c: Double;
begin
t1x := cy - y1;
t1y := x1 - cx;
t2x := y2 - cy;
t2y := cx - x2;
dx := (x1 + x2) / 2 - cx;
dy := (y1 + y2) / 2 - cy;
tx := 3 / 8 * (t1x + t2x);
ty := 3 / 8 * (t1y + t2y);
a := tx * tx + ty * ty;
b := dx * tx + dy * ty;
c := dx * dx + dy * dy - R * R;
D := b * b - a * c;
if D > 0 then begin
k := (Sqrt(D) - b) / a;
Pts[0] := Point(x1, y1);
Pts[3] := Point(x2, y2);
Pts[1] := Point(x1 + Round(k * t1x), y1 + Round(k * t1y));
Pts[2] := Point(x2 + Round(k * t2x), y2 + Round(k * t2y));
end;
end;
var
Pts: array [0 .. 3] of TPoint;
an1, an2: Double;
begin
an1 := 0;
an2 := Pi / 2;
Canvas.Pen.Color := clBlue;
Canvas.Pen.Width := 1;
Canvas.Ellipse(100, 100, 301, 301);
BezierArcByPoints(200 + Round(100 * Cos(an1)),
200 + Round(100 * Sin(an1)),
200 + Round(100 * Cos(an2)),
200 + Round(100 * Sin(an2)),
200, 200, 100, Pts);
Canvas.Pen.Color := clRed;
Canvas.Pen.Width := 3;
Canvas.PolyBezier(Pts);
end;
VBA Implementation of #Mbo's code:
I have no idea how this works without PI, or any Tan, but here it is:
I use it to draw curves, or arc of a circle using VBA in Excel. I was not able to figure out how to place the control points, but the code above just did it! I'm not gona pretend I understand any of it, but I figured the VBA implementation might be valuable, expecially since the documentation on the topic of the .addCurve method is extremely scarce.
Note that I've noticed the arc will progressively difform when using large rotations, but it's the only piece of code I was able to implement successfully.
Private Type Coordinates
x As long
y As long
End Type
Sub BezierArcByPoints(x1 As Long, _
y1 As Long, _
x2 As Long, _
y2 As Long, _
cx As Long, _
cy As Long, _
R As Double)
Dim t1x As Double
Dim t1y As Double
Dim t2x As Double
Dim t2y As Double
Dim dx As Double
Dim dy As Double
Dim k As Double
Dim tx As Double
Dim ty As Double
Dim D As Double
Dim a As Double
Dim b As Double
Dim c As Double
t1x = cy - y1
t1y = x1 - cx
t2x = y2 - cy
t2y = cx - x2
dx = (x1 + x2) / 2 - cx
dy = (y1 + y2) / 2 - cy
tx = 3 / 8 * (t1x + t2x)
ty = 3 / 8 * (t1y + t2y)
a = tx * tx + ty * ty
b = dx * tx + dy * ty
c = dx * dx + dy * dy - R * R
D = b * b - a * c
Dim c1 As Coordinates
Dim c2 As Coordinates
If D > 0 Then
k = (D ^ 0.5 - b) / a
c1.x = x1 + Round(k * t1x)
c1.y = y1 + Round(k * t1y)
c2.x = x2 + Round(k * t2x)
c2.y = y2 + Round(k * t2y)
Dim Pts(1 To 4, 1 To 2) As Single
Pts(1, 1) = x1
Pts(1, 2) = y1
Pts(2, 1) = c1.x ' Bezier control point 1
Pts(2, 2) = c1.y
Pts(3, 1) = c2.x ' Bezier control point 2
Pts(3, 2) = c2.y
Pts(4, 1) = x2
Pts(4, 2) = y2
End If
ActiveSheet.Shapes.AddCurve(SafeArrayOfPoints:=Pts).Select
Selection.ShapeRange.Fill.Visible = msoFalse
Selection.name = "objArc"
With Selection.ShapeRange.Line
.Visible = msoTrue
.DashStyle = msoLineSysDash
.ForeColor.RGB = RGB(255, 0, 0)
.Transparency = 0
End With
' If you have little circles to display where the control points are located
Dim objAnchor As Shape
Dim objAnchor2 As Shape
Set objAnchor = ActiveSheet.Shapes("objAnchor")
Set objAnchor2 = ActiveSheet.Shapes("objAnchor2")
Call setObjCoord(objAnchor, c1, True)
Call setObjCoord(objAnchor2, c2, True)
End Sub
Note that this function is only able to draw clockwise. If you have a negative rotation, make sure to reverse x1, y1 and x2, y2.
Example:
If Rotation < 0 Then
Call BezierArcByPoints(pointA.x, pointA.y, pointB.x, pointB.y, Axis.x, Axis.y, Radius)
Else
Call BezierArcByPoints(pointB.x, pointB.y, pointA.x, pointA.y, Axis.x, Axis.y, Radius)
End If

Create equilateral triangle in the middle of canvas?

I want to draw an equilateral triangle in the middle of canvas. I tried this:
ctx.moveTo(canvas.width/2, canvas.height/2-50);
ctx.lineTo(canvas.width/2-50, canvas.height/2+50);
ctx.lineTo(canvas.width/2+50, canvas.height/2+50);
ctx.fill();
But the triangle looks a bit too tall.
How can I draw an equilateral triangle in the middle of canvas?
Someone told me you have to find the ratio of the height of an equilateral triangle to the side of an equilateral triangle.
h:s
What are the two numbers?
The equation for the three corner points is
x = r*cos(angle) + x_center
y = r*sin(angle) + y_center
where for angle = 0, (1./3)*(2*pi), and (2./3)*(2*pi); and where r is the radius of the circle in which the triangle is inscribed.
You have to do it with the height of the triangle
var h = side * (Math.sqrt(3)/2);
or
var h = side * Math.cos(Math.PI/6);
So the ratio h:s is equal to:
sqrt( 3 ) / 2 : 1 = cos( π / 6 ) : 1 ≈ 0.866025
See : http://jsfiddle.net/rWSKh/2/
A simple version where X and Y are the points you want to top of the triangle to be:
var height = 100 * (Math.sqrt(3)/2);
context.beginPath();
context.moveTo(X, Y);
context.lineTo(X+50, Y+height);
context.lineTo(X-50, Y+height);
context.lineTo(X, Y);
context.fill();
context.closePath();
This makes an equilateral triange with all sides = 100. Replace 100 with how long you want your side lengths to be.
After you find the midpoint of the canvas, if you want that to be your triangle's midpoint as well you can set X = midpoint's X and Y = midpoint's Y - 50 (for a 100 length triangle).
The side lengths will not be equal given those coordinates.
The horizontal line constructed on the bottom has a length of 100, but the other sides are actually the hypotenuse of a 50x100 triangle ( approx. 112).
I can get you started with drawing an equilateral triangle but I don't have the time to get it centered.
jsFiddle
var ax=0;
var ay=0;
var bx=0;
var by=150;
var dx=bx-ax
var dy=by-ay;
var dangle = Math.atan2(dy, dx) - Math.PI / 3;
var sideDist = Math.sqrt(dx * dx + dy * dy);
var cx = Math.cos(dangle) * sideDist + ax;
var cy = Math.sin(dangle) * sideDist + ay;
var canvas = document.getElementById('equ');
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(ax,ay);
ctx.lineTo(bx,by);
ctx.lineTo(cx,cy);
ctx.fill();
my code for drawing triangle also depending on direction (for lines). code is for Raphael lib.
drawTriangle(x2 - x1, y2 - y1, x2, y2);
function drawTriangle(dx, dy, midX, midY) {
var diff = 0;
var cos = 0.866;
var sin = 0.500;
var length = Math.sqrt(dx * dx + dy * dy) * 0.8;
dx = 8 * (dx / length);
dy = 8 * (dy / length);
var pX1 = (midX + diff) - (dx * cos + dy * -sin);
var pY1 = midY - (dx * sin + dy * cos);
var pX2 = (midX + diff) - (dx * cos + dy * sin);
var pY2 = midY - (dx * -sin + dy * cos);
return [
"M", midX + diff, midY,
"L", pX1, pY1,
"L", pX2, pY2,
"L", midX + diff, midY
].join(",");
}

Categories

Resources