On a HTML canvas I have multiple points starting from 1 to N, this is basically a connect the dots application and is activated on touchstart.
There is validation so that they can only connect the dots from 1 and go to 2 (.. n). The issue is that right now is there is no validation that the line is a straight line and I am looking for an algorithm to do this, Here is what I have thought so far
For 2 points (x1,y1) to (x2,y2) get all the coordinates by finding the slope and using the formula y = mx + b
on touchmove get the x,y co-oridnates and make sure it is one of the points from the earlier step and draw a line else do not draw the line.
Is there a better way to do this or are there any different approaches that I can take ?
Edit: I originally misunderstood the question, it seems.
As far as validating the path: I think it would be easier just to have a function that determines whether a point is valid than calculating all of the values beforehand. Something like:
function getValidatorForPoints(x1, y1, x2, y2) {
var slope = (y2 - y1) / (x2 - x1);
return function (x, y) {
return (y - y1) == slope * (x - x1);
}
}
Then, given two points, you could do this:
var isValid = getValidatorForPoints(x1, y1, x2, y2);
var x = getX(), y = getY();// getX and getY get the user's new point.
if (isValid(x, y)) {
// Draw
}
This approach also gives you some flexibility—you could always modify the function to be less precise to accommodate people who don't quite draw a straight line but are tolerably close.
Precision:
As mentioned in my comment, you can change the way the function behaves to make it less exacting. I think a good way to do this is as follows:
Right now, we are using the formula (y - y1) == slope * (x - x1). This is the same as (slope * (x - x1)) - (y - y1) == 0. We can change the zero to some positive number to make it accept points "near" the valid line as so:
Math.abs((slope * (x - x1)) - (y - y1)) <= n
Here n changes how close the point has to be to the line in order to count.
I'm pretty sure this works as advertised and helps account for people's drawing the line a little crooked, but somebody should double check my math.
function drawGraphLine(x1, y1, x2, y2, color) {
var dist = Math.ceil(Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)));
var angle = Math.atan2(y2-y1, x2-x1)*180/Math.PI;
var xshift = dist - Math.abs(x2-x1);
var yshift = Math.abs(y1-y2)/2;
var div = document.createElement('div');
div.style.backgroundColor = color;
div.style.position = 'absolute';
div.style.left = (x1 - xshift/2) + 'px';
div.style.top = (Math.min(y1,y2) + yshift) + 'px';
div.style.width = dist+'px';
div.style.height = '3px';
div.style.WebkitTransform = 'rotate('+angle+'deg)';
div.style.MozTransform = 'rotate('+angle+'deg)';
}
// By Tomer Almog
Related
I am trying to calculate if a point resides above or below a line that is defined by two points. For clarification I only need to know if the point is on "this side" or "that side". I realize if the line is perfectly vertical there will be no "above" or "below". I have a line defined by two points (centerX, centerY) and (xprime, yprime). For simplicity centerX, centerY can be converted to (0,0). I want to determine if (mouseX, mouseY) is above or below that line.
I have tried to use this formula but it is not giving me the results I expected.
var position = Math.sin((xprime -centerX) * (mouseY - centerY) - (yprime - centerY) * (mouseX - prime));
The values for position seem to oscillate randomly from positive to negative as mouseX,mouseY values rotate around the line. I was under the impression the sign would change one time from positive to negative as the mouse position (mouseX,mouseY) crossed over the line. Values above the line would be positive, and values below would be negative.
I am using this code in conjunction with a formula to determine the angle of deflection from the original click. But I am not able to determine if the mouse is now above the initial click, or below. (again please excuse "above" and "below")
Simple solution exploiting cross product properties.
dx = xprime - centerX
dy = yprime - centerY
mx = mouseX - centerX
my = mouseY - centerY
cross = dx * my - dy * mx //zero means point on line
below = (cross > 0) //mouse is "at the right hand" of the directed line
if dx <> 0 then // check for vertical line
if dy/dx < 0 then //negative slope, invert result
below = not below
I'll try to give you general solution.
How to check if exact point is above or below line function:
Just imagine, we have line f(x) = 4x + 2. We need check, if point (x1, y1) below or above the line, we need to calculate f(x1) and compare it with y1.
if f(x1) > y1 it means, that (x1, y1) below the line.
if f(x1) < y1 means point (x1, y1) above the line.
if f(x1) = y1 - point on a line.
You can see on plot:
All line functions looks like f(x) = k * x + b, so, you need know k and b constants to know exact line function.
How to get line function by two points:
Imagine points A (x_a, y_a) and B (x_b, y_b) and we want to get line function, we need to solve system of two equations:
y_a = k * x_a + b
y_b = k * x_b + b
That is very easy. After you will know k and b and after you can check if point below or above AB line
I researched google but couldn't find the keywords for search. So I ask here if my algorithm and code is efficient?
http://sketchtoy.com/66429941 (algorithm)
The algoritm is: I have four points which are: north, east, south and west of circle. I check 4 distances (distanceToNorth, distanceToEast, distanceToSouth, distanceToWest). And I find minimum of them so that is the quarter.
Here is the code but it does not seem efficient for me.
(firstQuarter is North, secondQuarter is East and so on..
note: assume that mousemove is inside the circle.
var firstQuarterX = centerX;
var firstQuarterY = centerY - radius;
var secondQuarterX = centerX + radius;
var secondQuarterY = centerY;
var thirdQuarterX = centerX;
var thirdQuarterY = centerY + radius;
var fourthQuarterX = centerX - radius;
var fourthQuarterY = centerY;
var distanceToFirst = Math.sqrt(Math.pow(x-firstQuarterX, 2) + Math.pow(y-firstQuarterY, 2));
var distanceToSecond = Math.sqrt(Math.pow(x-secondQuarterX, 2) + Math.pow(y-secondQuarterY, 2));
var distanceToThird = Math.sqrt(Math.pow(x-thirdQuarterX, 2) + Math.pow(y-thirdQuarterY, 2));
var distanceToFourth = Math.sqrt(Math.pow(x-fourthQuarterX, 2) + Math.pow(y-fourthQuarterY, 2));
var min = Math.min(distanceToFirst, distanceToSecond, distanceToThird, distanceToFourth);
var numbers = [distanceToFirst, distanceToSecond, distanceToThird, distanceToFourth];
var index = numbers.indexOf(min); // it will give 0 or 1 or 2 or 3
var quarter = index + 1;
Observe that the boundaries between your quarters lie along the lines with equations y = x and y = -x, relative to an origin at the center of the circle. You can use those to evaluate which quarter each point falls in.
If your point is (x, y), then its coordinates relative to the center of the circle are xRelative = x - centerX and yRelative = y - centerY. Then
your point is in the first (south in your code) quarter if yRelative < 0 and Math.abs(xRelative) < -yRelative
your point is in the second (east) quarter if xRelative > 0 and Math.abs(yRelative) < xRelative
your point is in the third (north) quarter if yRelative > 0 and Math.abs(xRelative) < yRelative
your point is in the fourth (west) quarter if xRelative < 0 and Math.abs(yRelative) < -xRelative
I leave it to you to determine to which quarter to assign points that fall exactly on a boundary. Also, you can implement a little decision tree based on those criteria if you prefer; that should be a little more efficient then testing each criterion in turn.
Not so sure but I think this might work. Math.atan2(CenterY - y, CenterX - x) * 180 / Math.PI gives the apparent angle between the points. Do the remaining math to figure out the quarter.
What about something like:
return x>centerX?(y>centerY?"Quad 2":"Quad 1"):(y>centerY?"Quad 3":"Quad 4");
Less graceful, more slim.
For more efficient algorithm, you can compute the quadrant just by analyzing the signs of dx + dy and dx - dy quantities (dx, dy being x, y minus centerX, centerY respectively) (I presume that as your animation shows, your quadrants are rotated by 45 degrees against 'standard' quadrants.
I am working on a "rally" game where a car is drawing on hills made of cosine curves. I know the current xspeed of the car (without hills) but the problem is that I need to know the xspeed of the car on the hills to be able to draw the wheels on right places and keep the speed steady.
At the moment my solution looks like this.
function drawWheelOnBasicHill(hillStart, xLocWheel, wheelNro) {
var cw = 400 //the width of the hill
t_max = 2*Math.PI;
var scale = 80, step = cw, inc = t_max/step;
var t1 = (xLocWheel-hillStart)*inc
var y1 = -scale*0.5 * Math.cos(t1);
if(wheelNro == 1 ){ //backwheel
drawRotatedImage(wheel, car.wheel1x, car.wheel1y-y1-45,sx);
//drawing the wheel on canvas
} else { //frontwheel
drawRotatedImage(wheel, car.wheel2x, car.wheel2y-y1-45,sx);
}
for(var i=1; i<=car.speed; i++){ //finding the next xlocation of the wheel with the
//same distance (on the curve) to the previous location as the speed of the car(=the
//distance to the new point on the flat ground)
var t2 = (xLocWheel + i -hillStart)*inc
var y2 = -scale*0.5 * Math.cos(t2);
if(Math.round(Math.sqrt(i^2+(y2-y1)^2))==car.speed){
sx = sx+i; //the new xcoordinate break;
}
}
}
The for loop is the problem. It might bee too slow (animation with fps 24). I cant understand why the if statement isnt working at the moment. It works sometimes but most of the times the value of the condition newer reaches the actual xspeed.
Are there some more efficient and easier ways to do this? Or does this code contain some errors? I really appreciate your efforts to solve this! Ive been looking at this piece of code the whole day..
So i is the variable and
x2=x1+i
t2=t1+i*inc
y1=-scale*0.5 * Math.cos(t1)
y2=-scale*0.5 * Math.cos(t2)
which somehow is strange. The landscape should be time independent, that is, y should be a function of x only. The time step is external, determined by the speed of the animation loop. So a more logical model would have dx as variable and
dt = t2-t1
x2 = x1 + dx
y1 = f(x1) = -0.5*scale*cos(x1)
y2 = f(x2) = -0.5*scale*cos(x2)
and you would be looking for the intersection of
(x2-x1)^2+(y2-y1)^2 = (speed*dt)^2
which simplifies to
(speed*dt)^2=dx^2+0.25*scale^2*(cos(x1+dx)-cos(x1))^2
For small values of dx, which would be the case if dt or speed*dt is small,
cos(x1+dx)-cos(x1) is approx. -sin(x1)*dx
leading to
dx = (speed*dt) / sqrt( 1+0.25*scale^2*sin(x1)^2 )
To get closer to the intersection of curve and circle, you can then iterate the fixed point equation
dydx = 0.5*scale*(cos(x1+dx)-cos(x1))/dx
dx = (speed*dt) / ( 1+dydx^2 )
a small number of times.
There are numerous questions on SO similiar to this but not exactly as far as I could tell.
Want to move an object along a path at a slow pace. Here's an example of what I've got.
var moverateX,moverateY;
function setup (){
var opp=targetY-startY;
var adj=targetX-startX;
var hyp=Math.sqrt ((opp*opp)+(adj*adj));
moverateY=10*(opp/hyp);
moverateX=10*(adj/hyp);}
function okgo (){
obj.style.left=currentX+moverateX+'px';
obj.style.top=currentY+moverateY+'px';
setTimeout (function (){okgo ();},50);}
It works but moves too quickly. If I change the number the ratio is multiplied by to a smaller one, the object misses the end target. eg:
var moverateY=2*(opp/hyp);
var moverateX=2*(adj/hyp);
//moves slower but misses the end mark by a fair margin
Any ideas?
You can improve your code in two ways:
Using an absolute tracking instead of relative tracking
Using an absolute time control
Absolute tracking
Javascript uses doubles for all computations so you do not have a real accuracy problem, but the formulas for absolute tracking are more accurate AND easier: given (x0, y0) as starting point and (x1, y1) as ending point you can compute any point inbetween the two with:
x = x0 + t*(x1 - x0);
y = y0 + t*(y1 - y0);
where t goes from 0.0 (start) to 1.0 (end).
Absolute time control
This is where your code has a serious problem.
In Javascript (and in most other cases) when you set a timer you cannot be sure that the function will be called exactly what you want. The only thing you know for sure is the your function will not be called more than what you requested, but it's very common that some calls will be "late" in respect to the period you require.
To get a controlled movement over time you need to check the clock instead of just assuming the call time the expected one: to sum up...
function animate(div, x0, y0, x1, y1, speed) {
var dx = x1 - x0;
var dy = y1 - y0;
var dist = Math.sqrt(dx*dx + dy*dy);
var total_time = dist / speed;
var start_time = new Date().getTime();
var cback = setInterval(function() {
var now = new Date().getTime();
if (now >= start_time + total_time) {
// Animation is complete
div.style.left = x1 + "px";
div.style.top = y1 + "px";
//clearInterval(cback);
} else {
// We are still moving
var t = (now - start_time) / total_time;
div.style.left = (x0 + t*dx) + "px";
div.style.top = (y0 + t*dy) + "px";
}
}, 10);
}
Also the use of absolute timing simplifies for example "easing" (slow start and stop with acceleration and deceleration): just add after the computation of t the line
t = t*t*(3 - 2*t);
in this case the speed parameter means the average speed in px/ms.
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.