Finding x/y coordinates that fall within filled canvas arcs - javascript

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.

Related

How to consider polygon's corners

I have a canvas code which render polygon shapes. I can drag a figure and move it to another place. I have move event functions which change the color of the nearest polygons and the draggable polygon. The problem is that I don't know how to make them change their color only when I touching a corner of a figure with my draggable figure. I did what I did considering only width and height of a figure so when I drag a figure near the others some of them change their colors when I even don't touch them. So I should somehow consider their corners ... It's just too complicated to me.
There are relatively much code so I did codepen where you can see what I'm talking about.
PS: only javascript's API allowed.
There are many different ways to do hit detection, depending on what you want to detect, how efficiently you want to detect it etc. The link below gives some nice solutions to finding points inside arbitrary polygons by looking at the relationship between the point in question and any y axis intercepts.
http://alienryderflex.com/polygon/
By comparing the given point to the y intercepts, the counts on either side can be used to determine if there is a hit.
This is solved using a function like the one below, providing an array of x and y coordinates of the polygons vertices, as well as the x and y of the test point.
function pointInPolygon(xs, ys, x, y) {
var j = xs.length - 1
var oddNodes = false
for (var i=0; i<xs.length; i++) {
if ((ys[i] < y && ys[j] >= y ||
ys[j] < y && ys[i] >= y)
&& (xs[i] <= x || xs[j] <= x)) {
oddNodes ^= (xs[i] + (y - ys[i]) / (ys[j] - ys[i]) * (xs[j] - xs[i]) < x)
}
j=i
}
return oddNodes
}
I have created a fiddle that shows this in action (clicking on the poly will change its colour).
https://jsfiddle.net/95k3t26q/19/

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

Testing for intersection between two arcs

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.

Scale a dot's X Y on HTML5 canvas by percentage

I'm working on my first canvas project, and it requires a partial map of the US, with a zoom and center on a state when clicked.
I was able to find X Y arrays of points to draw the country, with each state being its own array. I needed the states to be drawn out larger then these dimensions, so I introduced a scale varaible to multiply each point by.
My next challenge was that the client only wanted 13 states drawn out, but not placed to scale against each other. (Example, put Ohio and Illinois next to each other on the canvas and ignore Indiana). My solution to that was to introduce a fixed X, Y "constant" for each state, that after the scaling happens, add the X Y value for that state and make that the spot to draw on.
for ( var j = 0; j < state.myPolygons.length; ++j) {
context.beginPath();
context.lineWidth = lineWidth;
context.strokeStyle = stateStroke;
context.fillStyle = stateFill;
for ( var k = 0; k < state.myPolygons[j].myXVals.length; ++k ) {
var x = parseFloat(state.myPolygons[j].myXVals[k]*state.scale)+state.posX;
var y = parseFloat(state.myPolygons[j].myYVals[k]*state.scale)+state.posY;
y = canvas.height - y;
if ( k == 0 )
context.moveTo(x,y);
else
context.lineTo(x,y);
}
context.closePath();
context.fill();
context.stroke();
}
The effect of clicking on a state, and growing it and centering on the canvas was accomplished by defining a target scale and number of steps. I get the difference between the target scale and current scale, and divide that by number of steps to figure out how much to add to the scale of the state at each "frame".
Example: Ohio's initial scale is 1.97 of the found coords. My target for Ohio scale is 3.75%. I get the difference (1.78), and divide that by 45 (the defined set of steps) to draw. This gives me 0.039 as an incrementer to my scale at each frame. I then loop through while my states current scale is less than the target scale. Again however, since I need to manipulate the X Y of the rendering, I have then a zoomx and zoomy constant for each state that gets added to the calculated X Y so it can "slide" to the center of the canvas.
All of this works perfectly and I have California zoom/sliding from left to right, Ohio sliding right to left, etc. --- Here is my problem.
I have a series of dots to indicate client loctions in the state. These are simple X Ys that I draw a circle on. The initial rendering of the map includes a loop to run through each states set of locations. I'm applying the same scale factor, and posX,posY variables to adjust final placement of the dot in relation to final rendering of the state
for (var loc in state.Locations) {
var locx = parseFloat(state.Locations[loc].x*state.scale)+state.posX
var locy =parseFloat(state.Locations[loc].y*state.scale)+state.posY;
var txt=state.Locations[loc].text;
var lnk=state.Locations[loc].link;
context.beginPath();
context.arc(locx,locy,locationSize,0,Math.PI*2,true);
context.fillStyle = locationFill;
context.closePath();
context.fill();
context.stroke();
}
When the state is zooming however, the scaling logic for the dots fails. The state scale for a given frame applies
x = parseFloat(activeState.myPolygons[j].myXVals[k]*activeState.scale)+activeState.posX;
y = parseFloat(activeState.myPolygons[j].myYVals[k]*activeState.scale)+activeState.posY;
When I apply this to a given location in the state with
locx = parseFloat(activeState.Locations[loc].x*activeState.scale)+activeState.posX;
locy = parseFloat(activeState.Locations[loc].y*activeState.scale)+activeState.posY;
I end up with X following pretty closely, but in Ohio's example, the Y is somewhere near Florida. Other states like California are even worse with their dots starting more "stacked" on top of each other and end up more "spread out" beside each other.
I'm trying to figure out the trig functions needed to grow and shrink the position of the X Y on a location in relation to the current scale of the state, and keep it on the same path the state is traveling on through the animation (both zooming in and zooming out).
My final attempt before coming here was to get the inital X Y of the location, and compare its distance to the LAST X Y of the state array. I was trying to then find the angle of the line connecting those 2 points, and then use all this to scale. I still feel that I may be onto something with this approach, I just can't make it happen.
Thank you everyone for taking the time to read this, I appriciate any help you can offer
You could just look at the paper I put on your desk, the one with the equation on it. However, SVGs would be more optimal for the project, as you could easily group things together using the g tag and then could just scale the entire group.
However, since you're forced to use canvas at this point: You would have to scale up and down director, using trig given the angle of the start point to location dot and the DIFFERENCE of left or right travelled from the original distance. I will explain in more detail, with actual equations, when you allow me to give me that paper back. However, the only line you really need to modify at this point is:
locy = parseFloat(activeState.Locations[loc].y*activeState.scale)+activeState.posY;

Categories

Resources