So I am once again dealing with annular sectors which is not my forte. I can use the .arc method on canvas just fine, the problem comes from needing my arc to be part of a path.
So for example:
ctx.save();
ctx.arc(centerX, centerY, radius, startAngle, endAngle, true);
ctx.stroke();
ctx.restore();
Works fine. But now I need it as part of a path, so I have something like this:
var pointArray = [...]; //this contains all four corner points of the annular sector
ctx.save();
ctx.moveTo(pointArray[0].x, pointArray[0].y);
ctx.lineTo(pointArray[1].x, pointArray[1].y); //so that draws one of the flat ends
ctx.arcTo(?, ?, pointArray[2].x pointArray[2].y, radius);
That tangent of tangent coordinate is driving me mad. Plus I have a serious concern:
http://www.dbp-consulting.com/tutorials/canvas/CanvasArcTo.html
Makes it sound like an arc drawn with arcTo could never cover 180degrees or more of a circle and there will be times that my annular sector will be greater than 180degrees.
Thanks for the help superior geometric minds of stackoverflow!
UPDATE
Ok, so I am having to do svg canvas inter-polarity here, and using coffee-script, actual production code follows!
annularSector : (startAngle,endAngle,innerRadius,outerRadius) ->
startAngle = degreesToRadians startAngle+180
endAngle = degreesToRadians endAngle+180
p = [
[ #centerX+innerRadius*Math.cos(startAngle), #centerY+innerRadius*Math.sin(startAngle) ]
[ #centerX+outerRadius*Math.cos(startAngle), #centerY+outerRadius*Math.sin(startAngle) ]
[ #centerX+outerRadius*Math.cos(endAngle), #centerY+outerRadius*Math.sin(endAngle) ]
[ #centerX+innerRadius*Math.cos(endAngle), #centerY+innerRadius*Math.sin(endAngle) ]
]
angleDiff = endAngle - startAngle
largeArc = (if (angleDiff % (Math.PI * 2)) > Math.PI then 1 else 0)
if #isSVG
commands = []
commands.push "M" + p[0].join()
commands.push "L" + p[1].join()
commands.push "A" + [ outerRadius, outerRadius ].join() + " 0 " + largeArc + " 1 " + p[2].join()
commands.push "L" + p[3].join()
commands.push "A" + [ innerRadius, innerRadius ].join() + " 0 " + largeArc + " 0 " + p[0].join()
commands.push "z"
return commands.join(" ")
else
#gaugeCTX.moveTo p[0][0], p[0][1]
#gaugeCTX.lineTo p[1][0], p[1][1]
##gaugeCTX.arcTo
#gaugeCTX.arc #centerX, #centerY, outerRadius, startAngle, endAngle, false
##gaugeCTX.moveTo p[2][0], p[2][1]
#gaugeCTX.lineTo p[3][0], p[3][1]
#gaugeCTX.arc #centerX, #centerY, innerRadius, startAngle, endAngle, false
THE SOLUTION
#gaugeCTX.moveTo p[0][0], p[0][1]
#gaugeCTX.lineTo p[1][0], p[1][1]
#gaugeCTX.arc #centerX, #centerY, outerRadius, startAngle, endAngle, false
#gaugeCTX.lineTo p[3][0], p[3][1]
#gaugeCTX.arc #centerX, #centerY, innerRadius, endAngle, startAngle, true #note that this arc is set to true and endAngle and startAngle are reversed!
I recently found myself disappointed by the arcTo() method (which really should have been called roundedCorner() ). I decided to come up with a general-purpose work-around for people who want to use the cx,cy,r,theta1,theta2 expression as well:
http://www.purplefrog.com/~thoth/art/paisley/arcTo.html
With the important bit of code copied in here:
/**
if code is "move" then we will do a moveTo x0,y0
if code is "line" then we will do a lineTo x0,y0
if code is anything else, we'll assume the cursor is already at x0,y0
*/
function otherArcTo(ctx, cx, cy, r, theta1, theta2, code)
{
console.log([cx,cy,r,theta1, theta2, code])
var x0 = cx + r*Math.cos(theta1)
var y0 = cy + r*Math.sin(theta1)
if (code=="move") {
ctx.moveTo(x0,y0)
} else if (code=="line") {
ctx.lineTo(x0,y0)
}
var dTheta = theta2-theta1
var nChunks = Math.ceil( Math.abs(dTheta) / (0.67*Math.PI) )
if (nChunks <=1) {
var theta3 = theta1 + dTheta/2
var r3 = r/Math.cos(dTheta/2)
var x1 = cx + r3*Math.cos(theta3)
var y1 = cy + r3*Math.sin(theta3)
var x2 = cx + r*Math.cos(theta2)
var y2 = cy + r*Math.sin(theta2)
ctx.arcTo(x1,y1,x2,y2, r)
} else {
for (var i=0; i<nChunks; i++) {
var code2 = null
if (i==0)
code2 = code
otherArcTo(ctx, cx, cy, r,
theta1 + dTheta*i/nChunks,
theta1 + dTheta*(i+1)/nChunks, code2)
}
}
}
While your question/code is not 100% clear to me,
arcTo() still has browser/rendering problems, so use arc() for now.
(Please forgive me, I can't give detailed link right now since I to became a victim of new forced firefox 12 crap and lost years of notes in my ff3.6 powered system, which it simply deleted during it's unapproved update).
arc() works with radians, so do a quick read-up on wiki how Math.PI relates to radians, then whip up some formula to convert your degrees (or what ever you might wish) to radians.
You'll be doing something like: (((2 * Math.PI) / 360) * 270) (=3/4 circle)
By the way: I did not ran into noticeable drawing problems with Radian/Unit conversions and ECMAscript's floating point behavior!
Also don't forget beginPath() and closePath() (and stroke() where needed): don't make the canvas guess!! This is usually key to drawing (closed) paths!!
You might also want to look at bezierCurveTo().
UPDATE (on TS' update):
Looking at your picture (which I guess is the rendering of your problem), I think I see what you want: pieces of pie-charts.
This is simple, they are 2 arcs and two lines between a beginPath() and closePath() (and a fill-color).
What you want to do is to center your origin (point 0,0) with translate(). Before you do this, read-up on getting crisp lines: the trick is to translate to half pixels: (x.5,y.5).
Then make one 'main-canvas' and one 'temp-canvas'. For each piece-of-pie, draw a on clean temp-canvas (just set it's width and height instead of some clear mumbo jumbo) and put this temp-canvas on your main-canvas. Lastly render/output your main-canvas. Done.
The 'magic' (plain math) in your script that does the translation between your existing svg-path I cannot help you with, since I('m ashamed to admit) don't recognize any javascript in your updated source.
Hope this helps!
Update 2: Actually.. if you would tell us the format of your points/coordinates array.. that would really help! Then we would know from where to where you are drawing.
The best solution MIGHT indeed be to whip-up a javascript function that accepts your points-array's..
This way your coffeescript could simply spit-out your known values (data-format) to the javascript that was needed anyways to render canvas (in html).
Which makes me think.. there must be existing svg-path to canvas translation-scripts .. right? Maybe some-one knows of a tried and tested one and can link/copy it here (for future reference)..
Update 3:
HINT: Don't forget: you can rotate the canvas in drawing-mode, but also when layering canvas'.
When you rotate (which works with the same radian-principle mentioned above) the canvas will rotate around it's origin-point (0,0) which is why translating (to the center of the canvas + 0.5px) is so useful for drawing these kind circle-based shapes!!!
I was having trouble with this myself. Once I drew it out on a piece of paper and used a little bit of geometry and trigonometry, it was pretty simple.
This function will help you calculate the points you need for the arcTo() function. You can move (translate) the arc by adding/subtracting from from the x and y at each point.
function calculateArcPoints(radius, rotation, sectionAngle) {
var halfSectionAngle = sectionAngle / 2;
return {
control: {
x: Math.cos(rotation) * radius / Math.cos(halfSectionAngle),
y: -1 * Math.sin(rotation) * radius / Math.cos(halfSectionAngle)
},
start: {
x: Math.cos(rotation - halfSectionAngle) * radius,
y: -1 * Math.sin(rotation - halfSectionAngle) * radius
},
end: {
x: Math.cos(rotation + halfSectionAngle) * radius,
y: -1 * Math.sin(rotation + halfSectionAngle) * radius
}
};
}
I used KineticJS and no SVG or coffee-script, so the rotation and translation was done outside the drawing function. Here's the full code on jsFiddle. I drew out multiple annular sections around a circle, but you can easily modify it to only draw one. Basically, you have an inner radius, an outer radius, and you connect each of them with straight lines at their start and end points.
That tangent of tangent coordinate is driving me mad. Plus I have a serious concern: http://www.dbp-consulting.com/tutorials/canvas/CanvasArcTo.html Makes it sound like an arc drawn with arcTo could never cover 180degrees or more of a circle and there will be times that my annular sector will be greater than 180degrees.
You are correct about the arcTo() function. It can only produce arcs that are less than 180 degrees. Tangent lines at >= 180° will never intersect, so there cannot be a control point for the arcTo() function. You could just draw two or (I would do three for an entire annulus) more adjacent to each other.
Related
Let's say I already have a bezier curve approximated by many straight lines (the bezier array in the code), and I would like to draw it with a series of rectangles. I have the following code below that does exactly this:
// don't change this array
const bezier = [{x:167.00,y:40.00},{x:154.37,y:42.09},{x:143.09,y:44.48},{x:133.08,y:47.15},{x:124.26,y:50.09},{x:116.55,y:53.27},{x:109.87,y:56.68},{x:104.15,y:60.31},{x:99.32,y:64.14},{x:95.28,y:68.15},{x:91.97,y:72.34},{x:89.31,y:76.67},{x:87.22,y:81.14},{x:85.63,y:85.74},{x:84.44,y:90.43},{x:83.60,y:95.22},{x:83.02,y:100.08},{x:82.63,y:105.00},{x:82.33,y:109.96},{x:82.07,y:114.94},{x:81.76,y:119.94},{x:81.33,y:124.93},{x:80.69,y:129.89},{x:79.77,y:134.82},{x:78.49,y:139.70},{x:76.78,y:144.50},{x:74.55,y:149.22},{x:71.74,y:153.84},{x:68.25,y:158.34},{x:64.03,y:162.71},{x:58.97,y:166.93},{x:53.02,y:170.98},{x:46.10,y:174.86},{x:38.11,y:178.54},{x:29.00,y:182.00}];
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const thickness = 35;
function rotateCanvas(x, y, a) {
ctx.translate(x, y);
ctx.rotate(a);
ctx.translate(-x, -y);
}
function drawRectangle(rX, rY, rW, rH, rA, color) {
ctx.beginPath();
rotateCanvas(rX + rW / 2, rY + rH / 2, rA);
ctx.rect(rX, rY, rW, rH);
rotateCanvas(rX + rW / 2, rY + rH / 2, -rA);
ctx.fill();
}
function calcRectFromLine(x1, y1, x2, y2) {
const dx = x2 - x1;
const dy = y2 - y1;
const mag = Math.sqrt(dx * dx + dy * dy);
const angle = Math.atan2(dy, dx);
return {
x: (x1 + x2) / 2 - mag / 2,
y: (y1 + y2) / 2 - thickness / 2,
w: mag,
h: thickness,
a: angle
};
}
function calculateRectangles() {
const result = [];
for (let i = 1; i < bezier.length; i++) {
const prev = bezier[i - 1];
const curr = bezier[i];
result.push(calcRectFromLine(prev.x, prev.y, curr.x, curr.y));
}
return result;
}
const rectangles = calculateRectangles();
for (let r of rectangles) {
drawRectangle(r.x, r.y, r.w, r.h, r.a);
}
<canvas width="400" height="400"></canvas>
If you run the snippet you'll see that the curve is not fully thick, and the fact that it is a series of rectangles is very obvious.
If you change the thickness parameter from 35 to a lower number and re-run it, it looks fine. It's only when it's very thick does this occur.
The code currently takes the bezier array, and creates a series of rotated rectangles and then renders them.
Is there any way to modify the calculateRectangles function to return a better approximation of the curve? Ideally it would still return a list of rectangles rotated around their center, but when rendered it would look more like the curve, and less like a list of rectangles.
The only idea I could think of is to somehow return twice as many rectangles from calculateRectangles, where each one is inverted from the previous one, such that both sides of the line are filled in, and while I think that might work, it unfortunately has the side-effect of returning twice as many rectangles, which is undesirable and I would to avoid it if possible.
The shapes that you should draw are not rectangles but quadrilaterals, obtained by joining the endpoints of the successive normals to the curve. Presumably, you can achieve that by means of Path objects.
In zones of high curvature, you may have to yet reduce the step, because the outer curve might not be smooth.
In fact, you can "flatten" a Bezier curve by choosing steps so that the deviation between successive segments remains bounded below a fixed tolerance.
In the case of a thick curve, you can keep that idea but making sure that the bounded deviation holds for both sides of the curve.
you can't really make a "thick bezier" by drawing rectangles, you're just going to end up with lots of gaps between them on one size, and weird looking overlap on the other side. If you want to stick with the polygon approximation, you'll need to use the normal at each of your points, and then draw lines to connect those. This leads to trapezoidal sections, so we can't use plain rects if we want decent looking results.
However, the bigger the offset, the more you're going to have problems in areas with tiny radius of curvature: you can already see one such problem just by looking at the normals crossing each other underneath the crest in the upper left, where we don't actually want to connect all normal-offset vertices, because one of them lies inside the shape we want to trace.
Alternatively, you can offset the bezier curve itself with "more beziers", e.g. https://pomax.github.io/bezierinfo/#offsetting, but even then you're still going to have to resolve overlaps in the offset shape.
Instead, you can approximate the curve using circular arcs, which make "thickening the curve" a bit easier because you simply use the same arc angles and center, with two different values for the radius to get your two offset segments.
This is an okay first attempt, but I'm going to keep trying. Simply add this to the end of the getRectangles function add further approximation rectangles. Seems good enough for my purposes (and simple!), but I'm going to keep investigating a bit. I'm aware it doesn't work perfectly, but it's okay, and I don't really need much better than okay:
let len = result.length;
for (let i = 1; i < len; i++) {
const prevR = result[i - 1];
const currR = result[i - 0];
result.push({
x: (prevR.x + currR.x) / 2,
y: (prevR.y + currR.y) / 2,
w: (prevR.w + currR.w) / 2,
h: (prevR.h + currR.h) / 2,
a: (prevR.a + currR.a) / 2
});
}
Actually, this is slightly better than okay the more and more I play with it. I think this might be a good enough solution. Unless someone can come up with something better.
Here's a GIF of the difference:
I am trying to move an object smoothly from point A to point B using HTML canvas and regular javascript.
Point A is a set of coordinates
Point B is in the case the cursor location.
I made a jsfiddle of what I have so far: https://jsfiddle.net/as9fhmw8/
while(projectile.mouseX > projectile.x && projectile.mouseY < projectile.y)
{
ctx.save();
ctx.beginPath();
ctx.translate(projectile.x, projectile.y);
ctx.arc(0,0,5,0,2*Math.PI);
ctx.fillStyle = "blue";
ctx.fill();
ctx.stroke();
ctx.restore();
if(projectile.mouseX > projectile.x && projectile.mouseY < projectile.y)
{
var stepsize = (projectile.mouseX - projectile.x) / (projectile.y - projectile.mouseY);
projectile.x += (stepsize + 1);
}
if(projectile.mouseY < projectile.y)
{
var stepsize = (projectile.y - projectile.mouseY) / (projectile.mouseX - projectile.x);
projectile.y -= (stepsize + 1);
}
}
Essentially what I can't figure out to do is to make the while loop slower (so that it appears animated in stead of just going through every iteration and showing the result).
I also can't figure out how to prevent the Arc from duplicating so that it creates a line that is permanent, instead of appearing to move from point a to point b.
Smooth animation here is really about determining how far to move your object for each iteration of the loop.
There is a little math involved here, but it's not too bad.
Velocity
Velocity in your case is just the speed at which your particles travel in any given direction over a period of time. If you want your particle to travel 200px over the course of 4 seconds, then the velocity would be 50px / second.
With this information, you can easily determine how many pixels to move (animate) a particle given some arbitrary length of time.
pixels = pixelsPerSecond * seconds
This is great to know how many pixels to move, but doesn't translate into individual X and Y coordinates. That's where vectors come in.
Vectors
A vector in mathematics is a measurement of both direction and magnitude. For our purposes, it's like combining our velocity with an angle (47°).
One of the great properties of vectors is it can be broken down into it's individual X and Y components (for 2-Dimensional space).
So if we wanted to move our particle at 50px / second at a 47° angle, we could calculate a vector for that like so:
function Vector(magnitude, angle){
var angleRadians = (angle * Math.PI) / 180;
this.magnitudeX = magnitude * Math.cos(angleRadians);
this.magnitudeY = magnitude * Math.sin(angleRadians);
}
var moveVector = new Vector(50, 47);
The wonderful thing about this is that these values can simply be added to any set of X and Y coordinates to move them based on your velocity calculation.
Mouse Move Vector
Modeling your objects in this way has the added benefit of making things nice and mathematically consistent. The distance between your particle and the mouse is just another vector.
We can back calculate both the distance and angle using a little bit more math. Remember that guy Pythagoras? Turns out he was pretty smart.
function distanceAndAngleBetweenTwoPoints(x1, y1, x2, y2){
var x = x2 - x1,
y = y2 - y1;
return {
// x^2 + y^2 = r^2
distance: Math.sqrt(x * x + y * y),
// convert from radians to degrees
angle: Math.atan2(y, x) * 180 / Math.PI
}
}
var mouseCoords = getMouseCoords();
var data = distanceAndAngleBetweenTwoPoints(particle.x, particle.y, mouse.x, mouse.y);
//Spread movement out over three seconds
var velocity = data.distance / 3;
var toMouseVector = new Vector(velocity, data.angle);
Smoothly Animating
Animating your stuff around the screen in a way that isn't jerky means doing the following:
Run your animation loop as fast as possible
Determine how much time has passed since last time
Move each item based on elapsed time.
Re-paint the screen
For the animation loop, I would use the requestAnimationFrame API instead of setInterval as it will have better overall performance.
Clearing The Screen
Also when you re-paint the screen, just draw a big rectangle over the entire thing in whatever background color you want before re-drawing your items.
ctx.globalCompositeOperation = "source-over";
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
Putting It All Together
Here is a Fiddle demonstrating all these techniques: https://jsfiddle.net/jwcarroll/2r69j1ok/3/
Here's an image to demonstrate the question:
Let's say I have Point A at [0,0], and Point B at [50, 30]. I want to find the coordinates of Point X, along a circle of radius 15, with an origin at Point A, which is also on a line between Point A and Point B.
Pointers on the best method to do this?
Since this has been tagged JavaScript, here's a simple implementation:
// disclaimer: code written in browser
function Point2D(x, y) {
this.x = x;
this.y = y;
}
function findCircleInteresction(center, radius, target) {
var vector = new Point2D(target.x - center.x, target.y - target.y);
var length = Math.sqrt(Math.pow(vector.x, 2) + Math.pow(vector.y, 2));
var normal = new Point2D(vector.x / length, vector.y / length);
var result = new Point2D(center.x + (normal.x * radius), center.y + (normal.y * radius));
return result;
}
findCircleInteresction(new Point2D(0, 0), 15, new Point2D(50, 30));
Point2D is just a class to make objects with x and y properties.
findCircleInteresction takes three parameters:
- center the center of the circle
- radius the radius of the circle
- target a point outside the circle
In findCircleInteresction:
- calculate the vector between the center and the target
- get the length of the resulting vector
- compute the normal (normalized) of the vector
- find the point where the vector intersects with the circle by adding the center of the circle plus the normalized vector components multiplied by the radius of the circle
This code could be heavily optimized and it's untested but I think it illustrated the idea.
You would want to think of this as two overlapping triangles, one with sides Bx-Ax and By-Ay. What you want is to find the coordinates of X, which would specifically be a triangle with sides Xx-Ax and Xy-Ay but with known hypotenuse R, which is your radius of the circle. Notice that the angle for both triangles are equal in respect to the x-coordinates-axis.
So to get the angle of the triangle, take the arctan(By-Ay/Bx-Ax) Now with that angle, call it T, you can solve for the smaller legs with your know radius R.
To get the x coordinate you would take Rcos(T)
To get the y coordinate you would take Rsin(T)
Bringing it all together you have that Xx = Rcos(T) and Xy = Rsin(T)
If you are not willing to use a Math library, which this method would use, you can use ratio's (as Pointy commented)
I want to make a Tetradecagon, a polygon with 14 sides, with Processing.JS.
(I want to make the Tetradecagon like the one shown in the Image below!)
Using the numbers given in the image, which I would like to replicate, I concluded that each piece (I don't know it's proper name), has an angle of 25.714285714°.....25 and 10/14 = 25 and 5/7 - 5/7 in decimal form = 0.714285714So, I arrived at 25.714285714°
Now, in Processing.JS, I was wanting to use a while loop:
var r = 0;
var draw = function() {
translate(200,200);
while(r < 361){
rotate(r);
r = r + 25.714285714;
line(200,0,200,200);
}
};
Here, I have set one variable, r. r will be the variable for the rotate() function. The while loop will keep going until r meets 360 - this will allow for the the change in r, the angle, to increase by 25.714285714°, while r < 361.
But, sadly, this is not happening. What I see on my canvas is the line being shot off the screen.
(edit) I added translate(200,200); just above the while() loop - this helped, but the lines are still not looking like the picture above.
The second point of the line is not staying in the center; the whole line is being shifted. I only want the first (top) point to be shifted by the given change in angles.
How do I change the code in order to achieve the goal that I am striving for? Any help would be appreciated - Thanks for your time!
P.S. This is my result with the current code -
Processing.js is just for running Processing code. This looks like a mix of Processing and Javascript code so my first advice would be "write real Processing code".
With that said, if you want to do this based on coordinate rotation, look at your 14-gon: it's 14 repeated triangles, so analyze one triangle and draw that 14 times. Any triangular slice is defined by a line from "the center" to "a vertex on the 14-gon" at a (necessary) distance r, the radius of the circumscribing circle. So, given a vertex (r,0) on the 14-gon where is the next vertex (nx,ny)?
Simple maths:
first vertex = (x, y) = (r,0)
next vertex = (nx,ny) = (r,0) rotated over (0,0) by (phi = tau/14)
(I'm using tau here because it's a far more convenient constant for programming purposes. It's simply equal to 2*pi, and as such represents an entire circle, rather than a half circle)
Now, computing that rotate coordinate using basic trigonometry:
nx = r * cos(phi) - 0 * sin(phi) = r * cos(phi)
ny = r * sin(phi) + 0 * cos(phi) = r * sin(phi)
Alright, done. And this nx,ny computation is clearly not specific to the number 14, it about arbitrary angles, so let's code the solution and make it work for any n-sided polygon:
void setup() {
size(400,400);
noLoop();
}
void draw() {
background(255);
// offset the coordinate system so that (0,0) is the sketch center
translate(width/2,height/2);
// then draw a polygon. In this case, radius width/2, and 14 sided
drawNgon(width/2, 14);
}
void drawNgon(float r, float n) {
// to draw (r,0)-(x',y') we need x' and y':
float phi = TAU/n;
float nx = r * cos(phi);
float ny = r * sin(phi);
// and then we just draw that line as many times as there are sides
for(int a=0; a<n; a++) {
// draw line...
line(r,0, nx,ny);
// rotate the entire coordinate system...
rotate(phi);
// repeat until done.
}
}
And now we can freely change both the polygon radius and the number of sides by changing the input to drawNgon(..., ...).
It's hard to explain the problem. but I'm gonna try my best.
First I make 2 lines, a line contain a start point, and a end point, like this
line = {
startPoint{x: , y:}
endPoint{x: , y:}
}
And then I draw the two lines on the canvas forming something like a corner of a triangle like this.
I now move the lines away from each other with the length Radius*2 like shown below
Then how can i now draw a arc using both endpoint as tangents point, like shown below
Do I need to use arc to this or can I do it with arcto? And if it's arc; how do I then give it of begin drawing and ending point so it draw it like shown on the image in the last figure. Thank for your time, any input helps. Sorry again for the bad description of the problem
UPDATE -
its seems i did not explain my problem fully. So here is a little update. Using the examples giving here. I end up with a Oval circle. an what i 'm trying to get is a round circle between the lines.
Yes, you can use arcTo():
Set your first line start point with moveTo()
then the intersection point between the two lines as first pair
then the end point of the second line (what you call "startpoint line 2") the last pair.
Provide a radius
To actually draw the last line (it's only used for calculation with arcTo()) add a lineTo() for the last point pair in the arc, stroke/fill.
If you want to move the lines apart but do not know the intersection point you have to manually calculate (see getIntersection() in that answer) it by first interpolating the lines beyond their original length.
var ctx = document.querySelector("canvas").getContext("2d");
ctx.moveTo(0, 0); // start point
ctx.arcTo(50, 150, 100, 0, 20); // intersection, outpoint, radius
ctx.lineTo(100, 0); // line from arc-end to outpoint
ctx.translate(130, 0);
ctx.moveTo(0, 0); // start point
ctx.arcTo(50, 150, 80, 50, 8); // intersection, outpoint, radius
ctx.lineTo(80, 50); // line from arc-end to outpoint
ctx.stroke();
<canvas></canvas>
Here is how you can extend the lines to find the intersection point if you move them apart without knowing the intersection point:
function extendLine(line, scale) {
var sx = line.startPoint.x,
sy = line.startPoint.y,
ex = line.endPoint.x,
ey = line.endPoint.y;
return {
startPoint: {x: sx, y: sy},
endPoint: {
x: sx + (ex - sx) * scale,
y: sy + (ey - sy) * scale
}
}
}
Scale can be a ridiculous value as we need to make sure that at very steep angles the lines will intersect somewhere. It does not affect calculation speed.
So then with the two lines (make sure the second line continues from first line's end-point, meaning you'll have to probably reverse the coordinates - if you want to do this dynamically you can measure the distance for each point in the second line to the end point of the first line, the shortest distance comes first as startPoint):
The execution steps would then be:
var line1 = ...,
line2 = ...,
line1tmp = extendLine(line1, 10000),
line2tmp = extendLine(line2, 10000),
ipoint = getIntersection(line1, line2); // see link above
// define the line + arcTo
ctx.moveTo(line1.startPoint.x, line1.startPoint.y);
ctx.arcTo(ipoint.x, ipoint.y,
line2.endPoint.x, line2.endPoint.y,
Math.abs(line2.startPoint.x - line1.endPoint.x) / 2);
ctx.lineTo(line2.endPoint.x, line2.endPoint.y);
ctx.stroke();
Given 2 line segments that meet at a common point, you can apply a rounded intersection to them using a cubic Bezier curve:
Here's how...
Given a point p1 and line segments that extend to from p1 to p0 (P10) and from p1 to p2 (P12):
var p0={x:50,y:50};
var p1={x:100,y:150};
var p2={x:250,y:100};
Calculate points on P10 & P12 that are a specified percent of the way from the common point (p1) back towards their respective starting points (p0 & p2):
var lerp=function(a,b,x){ return(a+x*(b-a)); };
var dx,dy,length;
var offsetPct=0.15;
// calc a point on P10 that is 15% of the way from p1 to p0
dx=p1.x-p0.x;
dy=p1.y-p0.y;
p00={ x:lerp(p1.x,p0.x,offsetPct), y:lerp(p1.y,p0.y,offsetPct) }
// calc a point on P12 that is 15% of the way from p1 to p2
dx=p1.x-p2.x;
dy=p1.y-p2.y;
p22={ x:lerp(p1.x,p2.x,offsetPct), y:lerp(p1.y,p2.y,offsetPct) }
Then you can draw your rounded intersection using the shortened line segments and a cubic Bezier curve:
Example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var p0={x:50,y:50};
var p1={x:100,y:150};
var p2={x:150,y:50};
roundedIntersection(p0,p1,p2,0.15);
function roundedIntersection(p0,p1,p2,offsetPct){
var lerp=function(a,b,x){ return(a+x*(b-a)); };
var dx,dy,length;
dx=p1.x-p0.x;
dy=p1.y-p0.y;
p00={ x:lerp(p1.x,p0.x,offsetPct), y:lerp(p1.y,p0.y,offsetPct) }
dx=p1.x-p2.x;
dy=p1.y-p2.y;
p22={ x:lerp(p1.x,p2.x,offsetPct), y:lerp(p1.y,p2.y,offsetPct) }
ctx.beginPath();
ctx.moveTo(p0.x,p0.y);
ctx.lineTo(p00.x,p00.y);
ctx.bezierCurveTo( p1.x,p1.y, p1.x,p1.y ,p22.x,p22.y);
ctx.lineTo(p2.x,p2.y);
ctx.stroke();
dot(p0.x,p0.y);
dot(p1.x,p1.y);
dot(p2.x,p2.y);
dot(p00.x,p00.y);
dot(p22.x,p22.y);
function dot(x,y){
ctx.beginPath();
ctx.arc(x,y,2,0,Math.PI*2);
ctx.closePath();
ctx.fill();
}
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=300 height=300></canvas>