Testing for intersection between two arcs - javascript

I have two arcs, defined by objects containing their start and end angles, their centres, and their radii.
The definition looks something like this:
arc = {
start: 0,
end: Math.PI,
radius: 10,
centre: [0, 0]
}
I want to know if the arcs intersect at any point, but I'm unsure how to do this. I've tried finding points of intersection between the two circles with the same radii and centres of the arcs, and then checking if those points are on both arcs, but this is complicated by the fact that my start/end angles might not be between 0 and 2π - they could be any value, and I'm not sure how to check whether the circle intersections lie on the arcs.
How can I check whether two arcs intersect? (For what it's worth, I'm using JavaScript, but any language would work as an answer)

Well, this is a bit old but needs an answer. But first, the arc structure does not have enough information to work with the question the way it was stated. There is nothing that tells if the arc goes around more than 2pi radians and how many times around. If it did, the idea of an arc of more than 2pi is geometrically absurd anyhow, and the possibility needs to be avoided. If you want to write a lot of code to handle non-geometric concepts here, that's beyond what I'll address and it's up to you to include more data in the structure and to test it properly. There is also nothing that tells us if the arc is defined as always being clockwise, counterclockwise. But the problem is easy to solve if the direction is well defined. For now, let's make the assumption that zero length arcs are never used and that arcs never go around more than 2pi radians. Let's also assume that arcs are always defined clockwise.
Now it's easy. Get up to two points that are the intersections of the two circles that the two arcs are on. Repeat the analysis for both points...
Get the angle of an intersection point. If an arc start angle is less than the end angle then just test to see if the intersection angle is at or between those two angles. But if the start angle is greater than the end angle of the arc then do two tests, one to see if the intersection angle is at or above the start angle and also at or below 2pi radians and also test, if needed, to see if the intersection angle is at or above zero and at or below the end angle of the arc.
I think that pseudo-code would look something like this:
on_arc = false;
if( start > end )
{
if( intersect >= start && intersect <= 2pi )
on_arc = true;
else if( intersect >= 0 && intersect <= end )
on_arc = true;
}
else if( start == end )
on_arc = true; // arc is the entire circle since we are not handling zero length arcs.
else
{
if( intersect >= start && intersect <= end )
on_arc = true;
}
If there is a flag in the arc structure to tell if the arc is clockwise or counterclockwise then the code could simply swap start and end angles before the tests.
Just avoid the >2pi arc idea because it's not useful. Whatever calculates the arc is either some sort of geometric 2D concept or it's something that can have a 2pi limit.

Start with the simpler case. How do you test for intersection of two circles. Two circles are going to intersect in zero places (too far apart), exactly one ( just touching ), two places ( overlapping ), or everywhere ( centers and radius are equal ).
Figure out how to calculate the one or two places circles would overlap, then figure out if either point is contained within both arcs.
For the last part, I would normalize the range so that start is between (-pi, pi] (the same return range as atan2()).
while ( start > M_PI ) { start -= 2 * M_PI, end -= 2 * M_PI ; }
Then use atan2() to find the angle that the point of intersection falls within the circle, and test that it is between start and end.
If you can get that to work, and want to improve performance further, there are ways using cross product to do a similar test without the trig calculations, but they require a certain number of special cases.

Related

Three JS check if position is behind object

I have a 3D character along with several predefined positions on the floor. Is there a way to check if a position is behind the 3D character and not in front or sideways?
I’ve included a rough sketch of what I’m trying to achieve (sorry for the poor drawing). Essentially I would like to return all of the positions of the red circles within the red lines and exclude all other circles outside of these two lines.
Is this possible? If so, is there any suggestion on how I can achieve this? I’m sorry but I don’t actually know which functions to use from Three JS for something like this or if it is possible.
Thank you!
Yes, it's possible.
You start out by first checking if a point/circle is behind the player. You do this by getting the dot product between the direction player is facing (a unit vector) and the direction vector to the circle (normalize it so that it's also a unit vector). If the values dotProduct <= 0 then the circle is behind your player.
(unit vector means that your vector has a magnitude of one. Which is a fancy way of saying your x/y/z will never go beyond 1)
Code example
// Let's assume that the following code is in some sort of loop, yes
// Get the direction vector to the circle
const directionVect = circle.position.clone().sub(player.position).normalize();
// If your player is a camera you can get the direction like so
const playerFacing = player.getWorldDirection(new THREE.Vector3());
// Orientation
if (playerFacing.dot(directionVect) <= 0) {
// Circle is behind the player
// ...to be continued...
} else {
return;
}
Now that you know which circles are behind your player, you can get the circles within the cone. This is done by getting the angle between the player's position and the circle's position. Then check that the angle fits some criteria (e.g. the angle can't be more than 45deg from back of player).
// ...
// Orientation
if (playerFacing.dot(directionVect) <= 0) {
// Circle is behind the player
const angle = player.position.angleTo(circle.position);
if (angle < Math.PI * 0.25) {
// Do something with circle
}
} else {
return;
}

How to implement a repulsion between 2D circles (paperjs)

I am making a paperjs app where you have circles and each circle can move freely. Some circles are connected to each other by means of lines which would cause the circles to come nearer to one another - ie the line simulates an elastic band between circles. However the circles are not allowed to overlap, so I want to make some kind of collision repulsion. Currently I have implemented this as a repulsive force between circles. For each circle I check the location of all other circles, and each circle's velocity vector is increased in the opposite direction to the circle close to it, in proportion to how close it is to this one. So in effect something like velocityvector += -(vectorFromThereToHere / 10)
However this has the effect that between the attractive force between connected circles and the repulsive force between all circles, you end up with a continual back and forth jittering.
What then would be the best way to implement some kind of repulsion between circles that wouldn't cause any juddering but would simply allow the circles' edges to touch one another whilst not coming any closer together? In effect I want the circles to simply bump into each other, not be allowed to slide over one another, but they are allowed to slide along each other's outside edge frictionlessly to get to where their momentum carries them.
You could implement an inelastic collision, followed by a position-fixing step. The idea is to apply an impulse on the objects in the direction of the normal of the impact.
// vx: velocity vector of object x
// ux: velocity vector of object x after impact
// mx: mass of the object x (1 if all objects are the same size)
// n: normal of impact (i.e.: p1-p2 in the case of circles)
// I: the coefficient of the impulse
// Equation of an inelastic collision
u1 * n = u2 * n
// Equations of the velocities after the impact
u1 = v1 + I * n / m1
u2 = v2 - I * n / m2
// solved for I:
I = (v1 - v2) * n / ((n*n)*(1/m1 + 1/m2))
When you have I you just have to apply the velocity changes. You might as well check if I > 0 before applying the impulses, to prevent the shapes stick together. Let's see how it works, and add position iterations if the balls start to overlap slowly after all these anyway.
PS: You might repeat the whole collision step in a single timeframe as well, to get better results when objects are involved in many collisions (because they are stuck together in a big ball)

How to calculate bezier curve control points that avoid objects?

Specifically, I'm working in canvas with javascript.
Basically, I have objects which have boundaries that I want to avoid, but still surround with a bezier curve. However, I'm not even sure where to begin to write an algorithm that would move control points to avoid colliding.
The problem is in the image below, even if you're not familiar with music notation, the problem should still be fairly clear. The points of the curve are the red dots
Also, I have access to the bounding boxes of each note, which includes the stem.
So naturally, collisions must be detected between the bounding boxes and the curves (some direction here would be good, but I've been browsing and see that there's a decent amount of info on this). But what happens after collisions have been detected? What would have to happen to calculate control points locations to make something that looked more like:
Bezier approach
Initially the question is a broad one - perhaps even to broad for SO as there are many different scenarios that needs to be taken into consideration to make a "one solution that fits them all". It's a whole project in its self. Therefor I will present a basis for a solution which you can build upon - it's not a complete solution (but close to one..). I added some suggestions for additions at the end.
The basic steps for this solutions are:
Group the notes into two groups, a left and a right part.
The control points are then based on the largest angle from the first (end) point and distance to any of the other notes in that group, and the last end point to any point in the second group.
The resulting angles from the two groups are then doubled (max 90°) and used as basis to calculate the control points (basically a point rotation). The distance can be further trimmed using a tension value.
The angle, doubling, distance, tension and padding offset will allow for fine-tuning to get the best over-all result. There might be special cases which need additional conditional checks but that is out of scope here to cover (it won't be a full key-ready solution but provide a good basis to work further upon).
A couple of snapshots from the process:
The main code in the example is split into two section, two loops that parses each half to find the maximum angle as well as the distance. This could be combined into a single loop and have a second iterator to go from right to middle in addition to the one going from left to middle, but for simplicity and better understand what goes on I split them into two loops (and introduced a bug in the second half - just be aware. I'll leave it as an exercise):
var dist1 = 0, // final distance and angles for the control points
dist2 = 0,
a1 = 0,
a2 = 0;
// get min angle from the half first points
for(i = 2; i < len * 0.5 - 2; i += 2) {
var dx = notes[i ] - notes[0], // diff between end point and
dy = notes[i+1] - notes[1], // current point.
dist = Math.sqrt(dx*dx + dy*dy), // get distance
a = Math.atan2(dy, dx); // get angle
if (a < a1) { // if less (neg) then update finals
a1 = a;
dist1 = dist;
}
}
if (a1 < -0.5 * Math.PI) a1 = -0.5 * Math.PI; // limit to 90 deg.
And the same with the second half but here we flip around the angles so they are easier to handle by comparing current point with end point instead of end point compared with current point. After the loop is done we flip it 180°:
// get min angle from the half last points
for(i = len * 0.5; i < len - 2; i += 2) {
var dx = notes[len-2] - notes[i],
dy = notes[len-1] - notes[i+1],
dist = Math.sqrt(dx*dx + dy*dy),
a = Math.atan2(dy, dx);
if (a > a2) {
a2 = a;
if (dist2 < dist) dist2 = dist; //bug here*
}
}
a2 -= Math.PI; // flip 180 deg.
if (a2 > -0.5 * Math.PI) a2 = -0.5 * Math.PI; // limit to 90 deg.
(the bug is that longest distance is used even if a shorter distance point has greater angle - I'll let it be for now as this is meant as an example. It can be fixed by reversing the iteration.).
The relationship I found works good is the angle difference between the floor and the point times two:
var da1 = Math.abs(a1); // get angle diff
var da2 = a2 < 0 ? Math.PI + a2 : Math.abs(a2);
a1 -= da1*2; // double the diff
a2 += da2*2;
Now we can simply calculate the control points and use a tension value to fine tune the result:
var t = 0.8, // tension
cp1x = notes[0] + dist1 * t * Math.cos(a1),
cp1y = notes[1] + dist1 * t * Math.sin(a1),
cp2x = notes[len-2] + dist2 * t * Math.cos(a2),
cp2y = notes[len-1] + dist2 * t * Math.sin(a2);
And voila:
ctx.moveTo(notes[0], notes[1]);
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, notes[len-2], notes[len-1]);
ctx.stroke();
Adding tapering effect
To create the curve more visually pleasing a tapering can be added simply by doing the following instead:
Instead of stroking the path after the first Bezier curve has been added adjust the control points with a slight angle offset. Then continue the path by adding another Bezier curve going from right to left, and finally fill it (fill() will close the path implicit):
// first path from left to right
ctx.beginPath();
ctx.moveTo(notes[0], notes[1]); // start point
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, notes[len-2], notes[len-1]);
// taper going from right to left
var taper = 0.15; // angle offset
cp1x = notes[0] + dist1*t*Math.cos(a1-taper);
cp1y = notes[1] + dist1*t*Math.sin(a1-taper);
cp2x = notes[len-2] + dist2*t*Math.cos(a2+taper);
cp2y = notes[len-1] + dist2*t*Math.sin(a2+taper);
// note the order of the control points
ctx.bezierCurveTo(cp2x, cp2y, cp1x, cp1y, notes[0], notes[1]);
ctx.fill(); // close and fill
Final result (with pseudo notes - tension = 0.7, padding = 10)
FIDDLE
Suggested improvements:
If both groups' distances are large, or angles are steep, they could probably be used as a sum to reduce tension (distance) or increase it (angle).
A dominance/area factor could affect the distances. Dominance indicating where the most tallest parts are shifted at (does it lay more in the left or right side, and affects tension for each side accordingly). This could possibly/potentially be enough on its own but needs to be tested.
Taper angle offset should also have a relationship with the sum of distance. In some cases the lines crosses and does not look so good. Tapering could be replaced with a manual approach parsing Bezier points (manual implementation) and add a distance between the original points and the points for the returning path depending on array position.
Hope this helps!
Cardinal spline and filtering approach
If you're open to use a non-Bezier approach then the following can give an approximate curve above the note stems.
This solutions consists of 4 steps:
Collect top of notes/stems
Filter away "dips" in the path
Filter away points on same slope
Generate a cardinal spline curve
This is a prototype solution so I have not tested it against every possible combination there is. But it should give you a good starting point and basis to continue on.
The first step is easy, collect points representing the top of the note stem - for the demo I use the following point collection which slightly represents the image you have in the post. They are arranged in x, y order:
var notes = [60,40, 100,35, 140,30, 180,25, 220,45, 260,25, 300,25, 340,45];
which would be represented like this:
Then I created a simple multi-pass algorithm that filters away dips and points on the same slope. The steps in the algorithm are as follows:
While there is a anotherPass (true) it will continue, or until max number of passes set initially
The point is copied to another array as long as the skip flag isn't set
Then it will compare current point with next to see if it has a down-slope
If it does, it will compare the next point with the following and see if it has an up-slope
If it does it is considered a dip and the skip flag is set so next point (the current middle point) won't be copied
The next filter will compare slope between current and next point, and next point and the following.
If they are the same skip flag is set.
If it had to set a skip flag it will also set anotherPass flag.
If no points where filtered (or max passes is reached) the loop will end
The core function is as follows:
while(anotherPass && max) {
skip = anotherPass = false;
for(i = 0; i < notes.length - 2; i += 2) {
if (!skip) curve.push(notes[i], notes[i+1]);
skip = false;
// if this to next points goes downward
// AND the next and the following up we have a dip
if (notes[i+3] >= notes[i+1] && notes[i+5] <= notes[i+3]) {
skip = anotherPass = true;
}
// if slope from this to next point =
// slope from next and following skip
else if (notes[i+2] - notes[i] === notes[i+4] - notes[i+2] &&
notes[i+3] - notes[i+1] === notes[i+5] - notes[i+3]) {
skip = anotherPass = true;
}
}
curve.push(notes[notes.length-2], notes[notes.length-1]);
max--;
if (anotherPass && max) {
notes = curve;
curve = [];
}
}
The result of the first pass would be after offsetting all the points on the y-axis - notice that the dipping note is ignored:
After running through all necessary passes the final point array would be represented as this:
The only step left is to smoothen the curve. For this I have used my own implementation of a cardinal spline (licensed under MIT and can be found here) which takes an array with x,y points and smooths it adding interpolated points based on a tension value.
It won't generate a perfect curve but the result from this would be:
FIDDLE
There are ways to improve the visual result which I haven't addressed, but I will leave it to you to do that if you feel it's needed. Among those could be:
Find center of points and increase the offset depending on angle so it arcs more at top
The end points of the smoothed curve sometimes curls slightly - this can be fixed by adding an initial point right below the first point as well at the end. This will force the curve to have better looking start/end.
You could draw double curve to make a taper effect (thin beginning/end, thicker in the middle) by using the first point in this list on another array but with a very small offset at top of the arc, and then render it on top.
The algorithm was created ad-hook for this answer so it's obviously not properly tested. There could be special cases and combination throwing it off but I think it's a good start.
Known weaknesses:
It assumes the distance between each stem is the same for the slope detection. This needs to be replaced with a factor based comparison in case the distance varies within a group.
It compares the slope with exact values which may fail if floating point values are used. Compare with an epsilon/tolerance

Algorithm for moving an object horizontally in javascript

I am currently working on a game using javascript and processing.js and I am having trouble trying to figure out how to move stuff diagonally. In this game, there is an object in the center that shoots other objects around it. Now I have no problem moving the bullet only vertically or only horizontally, however I am having difficulty implementing a diagonal motion for the bullet algorithm.
In terms of attempts, I tried putting on my math thinking cap and used the y=mx+b formula for motion along a straight line, but this is what my code ends up looking like:
ellipse(shuriken.xPos, shuriken.yPos, shuriken.width, shuriken.height); //this is what I want to move diagonally
if(abs(shuriken.slope) > 0.65) {
if(shuriken.targetY < shuriken.OrigYPos) {
shuriken.yPos -= 4;
} else {
shuriken.yPos += 4;
}
shuriken.xPos = (shuriken.yPos - shuriken.intercept)/shuriken.slope;
} else {
if(shuriken.targetX < shuriken.OrigXPos) {
shuriken.xPos -= 4;
} else {
shuriken.xPos += 4;
}
shuriken.yPos = shuriken.slope * shuriken.xPos + shuriken.intercept;
}
The above code is very bad and hacky as the speed varies with the slope of the line.
I tried implementing a trigonometry relationship but still in vain.
Any help/advice will be greatly appreciated!
Think of it this way: you want the shuriken to move s pixels. If the motion is horizontal, it should move s pixels horizontally; if vertical, s pixels vertically. However, if it's anything else, it will be a combination of pixels horizontally/vertically. What's the correct combination? Well, what shape do you get if you project s distance in any direction from a given point? That's right, a circle with radius s. Let's represent the direction in terms of an angle, a. So we have this picture:
How do we get the x and the y? If you notice, we have a triangle. If you recall your trigonometry, this is precisely what the sine, cosine, and tangent functions are for. I learned their definitions via the mnemonic SOHCAHTOA. That is: Sin (a) = Opposite/Hypotenuse, Cos(a) = Adjacent/Hypotenuse, Tan(a) = Opposite/Adjacent. In this case, opposite of angle a is y, and adjacent of angle a is x. Thus we have:
cos(a) = x / s
sin(a) = y / s
Solving for x and y:
x = s * cos(a)
y = s * sin(a)
So, given the angle a, and that you want to move your shuriken s pixels, you want to move it s * cos(a) horizontally and s * sin(a) vertically.
Just be sure you pass a in radians, not degrees, to javascript's Math.sin and Math.cos functions:
radians = degrees * pi / 180.0
This may be why your trigonometric solution didn't work as this has bitten me a bunch in the past.
If you know the angle and speed you are trying to move at, you can treat it as a polar coordinate, then convert to cartesian coordinates to get an x,y vector you would need to move the object by to go in that direction and speed.
If you don't know the angle, you could also come up with the vector by taking the difference in X and difference in Y (this I know you can do as you are able to calculate the slope between the 2 points). Then take the resulting vector and divide by the length of the vector to get a unit vector, which you can then scale to your speed to get a final vector in which you can move your object by.
(This is what probably what kennypu means by sticking with vectors?)

Finding x/y coordinates that fall within filled canvas arcs

Here is the fiddle
The arcs I've drawn around the outside of the circle - I'd like to know how to find all of the x/y coordinates that they cover so that I don't have to re-draw them every time using isPointInPath() to determine whether the mouse cursor is over them or not. I was thinking about writing all of the x/y coordinates to an array that I could check against the mouse position x/y coordinates and if I find a match then I change the cursor. The problem is, I don't know the code to derive all of the x/y values.
You don't actually have to redraw your arcs to use .isPointInPath()-- just omit any calls to .fill() or .stroke() and you'll have a path which you can use to test whether it contains a point.
I would suggest having one function which outlines the arc path (.beginPath(), path commands, .closePath()) and then two functions which call it-- one which calls the arc path function, then sets the fill style and fills the path to draw it, and another which calls the arc path function and then just tests whether a point is in the path.
This is the method you should use:
http://en.wikipedia.org/wiki/Point_in_polygon
The way it works is actually extremely simple: if the amount of times a ray that ends at any point passes through the polygon perimeter is even, the respective point HAS to be outside of the polygon. If it's odd, it's within the polygon.
Here's a function found by Pimvdb:
function isPointInPoly(poly, pt){
for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
((poly[i].y <= pt.y && pt.y < poly[j].y) || (poly[j].y <= pt.y && pt.y < poly[i].y))
&& (pt.x < (poly[j].x - poly[i].x) * (pt.y - poly[i].y) / (poly[j].y - poly[i].y) + poly[i].x)
&& (c = !c);
return c;
}
I wouldn't call what you have 'arcs' (they're more like bowed rectangles), but here's a broad sketch of how to write a function to determine if a point is within such a thing:
Calculate the center of the circle from the end points and radius.
If the point is closer to the center than the close arc (distance-to-center-squared is greater than close-radius-squared) then return false.
If the point is farther from the center than the far arc then return false.
Calculate the start and end angles for the endpoints of your rectangles with respect to the center of the circle. (Hint: Math.atan2)
Calculate the angle for the point with respect to the center of the circle. If it is not between the angles for the end points, return false.
Beware endpoints that cross the wrapping values for Math.atan2.
Return true if other tests passed.
You can't calculate "all" points in this region, as there an an infinite number of them. Creating a lookup table of all integer pixel coordinates in your image is possible, but wasteful.
What I would do, instead of what you are proposing, is use a retained-mode graphics system (hint: SVG) and let it track mouse events for me using its far-more-efficient code.
Here's an easier method:
I've altered the drawtabs function to also check if mouse is within a tab:
http://jsfiddle.net/kkDqz/4/
WARNING
This method is easy, but requires you to redraw EVERYTHING on mousemove.

Categories

Resources