How to get a better approximation of a thick bezier curve? - javascript

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:

Related

Rotation facing mouse not correct with p5 libraries

i am making a javascript shooter game.i want the the player to rotate towards the mouse.it is working, but the rotation was not correct.
I tried this with an image, and it works, but with the sprite itself(player1), is not.
i have asked this once before but received no answer
I am a beginner in javascript, so help would be appreciated.
I am using the p5.js libraries
Here is my code snippet:
//variables
var player1;
var gun1;
var gun2;
function preload(){
img = loadImage('rect1.png');
}
function setup(){
//creating sprites
player1 = createSprite(200,200,30,30)
gun = createSprite(player1.x,player1.y-20,5,30)
gun.shapeColor = "black"
player1.addImage("player",img)
player1.scale = 0.2
}
function draw(){
canvas = createCanvas(displayWidth-20, displayHeight-120);
background("#32CD32");
push()
gun.x = player1.x;
gun.y = player1.y-15;
// functions to move
//up
if(keyDown("up")){
player1.y = player1.y - 5;
}
//down
if(keyDown("down")){
player1.y = player1.y + 5;
}
//right
if(keyDown("right")){
player1.x = player1.x + 5;
}
//left
if(keyDown("left")){
player1.x = player1.x - 5;
}
angleMode(DEGREES)
imageMode(CENTER)
let a = atan2(mouseY - height / 2, mouseX - width / 2);
translate(width/2, height/2);
//rotate(a)
player1.rotation = a
//image(img,0,0,40,40)
pop()
drawSprites();
}
I think I'm using a dated version of p5.play, so there's not much in your code that works for me, but here's what I think is going on based on what you're saying.
If you want to understand what the deal is with atan2(), you first have to understand atan(). Basically, you have the ordinary trig functions sin, cos, and tan. Then you have the inverse trig functions arcsin, arccos, and arctan (abbreviated asin, acos, and atan). The arctan function is useful because you can input a slope and it will give you the angle of that slope. There's a catch, though; atan will only give values between -pi/2 and pi/2. This covers all non-vertical lines, but what if you wanted to use it for a vector or something that has direction? atan2() solves that problem. Instead of taking one input (a ratio: rise/run), it takes two inputs, a rise and a run. This prevents dividing by zero (for vertical lines) and signs of rise and run cancelling. The first input is the rise, and the second is the run. The output is the angle between the vector with those coordinates and the x-axis. So atan2() will give you some angle between -pi and pi.
Now let's look at what you have put into the atan2() function:
atan2(mouseY - height / 2, mouseX - width / 2);
So the vector you're considering is the vector from the middle of the canvas to the mouse. If this is what you want, great. If it's not, maybe consider
atan2(mouseY - player1.y, mouseX - player1.y);
which yields the "heading" (not really the heading) of the vector from the player's position to the mouse.
There are a couple of other potential problems (I can't figure out which one it is because p5.play isn't behaving, or I'm doing something else wrong):
radians/degrees: sometimes this stuff goes wrong. Try printing a and seeing if it's what you're looking for. If it's in degrees, consider saying player1.rotation = radians(a) instead. I know that p5.Vector.fromAngle() doesn't care about angleMode, maybe sprite.rotation doesn't either?
I don't know how drawSprites works, but you might consider putting it inside of the push()/pop() section. When drawing other shapes, this is how you get a rotation about the point (x, y):
let x = 100;
let y = 200;
let a = atan2(mouseY - y, mouseX - x);
push();
translate(x, y);
rotate(a);
square(-10, -10, 20);
pop();
The square is centered at (x,y), and is rotated about (x,y) toward the cursor. It's necessary that you do it in this order: push, translate, rotate, shape, pop. If you mix up that order, it doesn't do it right. But what you have is push, translate, pop, shape. I don't know how sprite.rotate works, so maybe it's supposed to do what you want. But here is another way to do a rotation. (If you're going to do it this way, I think you'd have to draw player1 "at (0,0)" after the translation and rotation, before pop)
I hope this helped!

Moving object from A to B smoothly across canvas

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/

How can I make a tetradecagon with Processing.Js?

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(..., ...).

Canvas ArcTo confusion

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.

How to define the intersection of three circles?

Given three circles with their center point and radius, how can you define the area of intersection?
So far what I have is:
var point1 = {x: -3, y: 0};
var point2 = {x: 3, y: 0};
var point3 = {x: 0, y: -3};
var r1 = 5;
var r2 = 5;
var r3 = 5;
var area = returnIntersectionArea(point1, point2, point3, r1, r2, r3);
Also, if two collide but not the third, the function should return null.
If none collide, null should be returned.
This article describes how to find the area of the intersection between two circles. The result it easily extended to three circles.
-------------EDIT-------------
OK, the problem is not easily extended to three circles, I found PhD theses on the subject. Assuming the three circles intersect as shown below, an approximate solution can be found (I think). Before we attempt it, we must check if the three circles indeed intersect as shown below. The problem changes quite a bit if say one circle is inside the other and the third intersects them both.
.
Let S1,S2 and S3 denote the areas of the three circles, and X1,X2 and X3 denote the area of the intersections between each pair of circles (index increases in clockwise direction). As we already established, there are exact formulae for these. Consider the following system of linear equations:
A+D+F+G = A+D+X1 = S1
B+D+E+G = B+D+ X3 = S2
B+E+D+G = B+E+X2 = S3
It is underdetermined, but an approximate solution can be found using least squares. I haven't tried it numerically but will get back to you as soon as I do :D
If the least-squares solution seems wrong, we should also impose several constraints, e.g. the area if the intersection between any pair of circles is smaller than the area of the circles.
Comments are appreciated.
PS +1 to Simon for pointing out I shouldn't qualify things as easy
One way of approaching this problem is via a Monte Carlo simulation:
function returnIntersectionArea(point1, point2, point3, r1, r2, r3) {
// determine bounding rectangle
var left = Math.min(point1.x - r1, point2.x - r2, point3.x - r3);
var right = Math.max(point1.x + r1, point2.x + r2, point3.x + r3);
var top = Math.min(point1.y - r1, point2.y - r2, point3.y - r3);
var bottom = Math.max(point1.y + r1, point2.y + r2, point3.y + r3);
// area of bounding rectangle
var rectArea = (right - left) * (bottom - top);
var iterations = 10000;
var pts = 0;
for (int i=0; i<iterations; i++) {
// random point coordinates
var x = left + Math.rand() * (right - left);
var y = top + Math.rand() * (bottom - top);
// check if it is inside all the three circles (the intersecting area)
if (Math.sqrt(Math.pow(x - point1.x, 2) + Math.pow(y - point1.y, 2)) <= r1 &&
Math.sqrt(Math.pow(x - point2.x, 2) + Math.pow(y - point2.y, 2)) <= r2 &&
Math.sqrt(Math.pow(x - point3.x, 2) + Math.pow(y - point3.y, 2)) <= r3)
pts++;
}
// the ratio of points inside the intersecting area will converge to the ratio
// of the area of the bounding rectangle and the intersection
return pts / iterations * rectArea;
}
The solution can be improved to arbitrary precision (within floating-point limits) by increasing the number of iterations, although the rate at which the solution is approached may become slow. Obviously, choosing a tight bounding box is important for achieving good convergence.

Categories