Find the better intersection of two moving objects - javascript

I would like to optimize dramaticaly one of my algorithm, i will try to explain it the best way that i can.
The subject
We are in a 2D euclidian system at the time t = 0.
In this system there is two object : O1 and O2.
O1 and O2 are respectively situated at the point PA and PC.
O1 moves at a constant and known speed in direction of the point PB. The object will stop when it reach PB.
O2 can move at a constant and known speed different or not of O1's in any direction. At the time 0, O2 has no direction, we will need to find one for it.
The knowns parameters:
O1 : Position, direction, speed
O2 : Position, speed
Here is a little diagram of the system.
We would like to find the point PI and the time ti for which : Position of O1 at the time ti = Position of O2 at the time ti = PI. Then we will make the object O2 move to the point PI to get the O2 direction.
When the direction of O2 (the point PI) is chosen and both objects O1 and O2 are on the move, the objects will never stop or wait for each other.
In this case, the result would be something like this (PI is noted D on this picture).
The algorithm
You can find the working algorithm written in JS at this jsfiddle, it is also a great way to understand the problem.
At this time i use a simple algorithm who works, but can take a lot of operations, i will get the best intersection time, and get the intersection position afterwards.
To get this time, i will check the position of O1 at a moment, and check if O2 could possibly go to this position at this time. If O2 could not reach the object in time, we will increase the time by 150%, however if O2 could cross the O1-B line at the time, we will decrease the time by 50%.
Eventually, after many approximations, we will find the perfect time where both objects could meet.
PseudoCode
function getOptimalIntersectionTime time
if distance between O1 and O2 at the time `time` < 1
return time
else if O2 could not reach the object O1 at the time `time`
return getOptimalIntersectionTime time * 1.5
else
return getOptimalIntersectionTime time * 0.5
Why am I concern ?
My algorithm works, but in some case (e.g. the "Reverse Case" in the jsFiddle) it will take a large amount of calculus to find the best point.
In this jsFiddle, we are using little values for position (-1000 to 1000) and speed (1-200) but this algorithm is dramaticaly slower with bigger numbers.
I know that premature optimization is a bad idea, but I'm at the end of the project (which consists on databases insertions / selection and data analysis, including this algorithm called many many times) and this algorithm take up to 80% of the project system ressources in certain cases so an improvement could really improve the stability and the responsiveness of the system.

Without loss of generality, let O2 be located at (0,0).
Let s and v the location and velocity vectors of O1, v2 the speed of O2, and t the time to intercept. We then have:
|s + v * t| = t * v2
By the definition of distance:
(sx + vx * t) ^ 2 + (sy + vy * t) ^ 2 = (t * v2) ^ 2
Multiplying this out and reordering terms gives:
sx^ 2 + 2 * sx * vx * t + vx^2 * t^2
+ sy^ 2 + 2 * sy * vy * t + vy^2 * t^2
- v2^2 * t^2
= 0
i.e.
sx^2 + sy^2 + (2 * sx * vx + 2 * sy * vy) * t + (vx^2 + vy^2 - v2^2) * t^2 = 0
\--- ---/ \------------ ----------/ \-------- ------/
\ / \ / \ /
c b a
As you can see, this a quadratic equation in t. We can simply apply the quadratic formula to find the two possible values for t (if the equation has no solution, that's because no interception is possible). You'll probably want to use the earliest future interception, i.e. the smaller t that is > 0.
Once you have computed the t, finding the interception point and from that the interception direction should be easy.
To summarize, this problem can be solved in constant time, no iteration is necessary.

You appear to be over-thinking the problem, it should just be simple geometry.
Leaving aside the problem of how you define the nearest point, let's solve for the situation where the desired point is midway between PA and PB.
We have to assume a time period for the entire cycle, let's call that T.
PI = (PB - PA) / 2; // simplified
TI = T / 2; // simplified
[decompose all formulae for the x and y coordinates separately].
There are relatively simple formulae for determining the closest intersection of a point (PC) with a line (PA -> PB), although how that's defined is complicated when that line isn't infinitely long.
Then you need:
V1 = (PB - PA) / T; // O1's velocity
V2 = (PI - PC) / T; // O2's velocity
These last two lines don't depend on the earlier assumptions - if you know the interception point then the velocity is simply the distance travelled divided by the time taken.
Hence unless you impose some additional constraints on V2, there is always a solution and it's calculated in a few trivial math operations.

Update: #Meriton's later answer is better than mine. I recommend trying his first.
As you realize, we have three, simultaneous equations in the three unknowns vx2, vy2 and t -- respectively the x and y velocities of 02, and time. The equations unfortunately are not all linear:
x1o + vx1*t == x2o + vx2*t
y1o + vy1*t == y2o + vy2*t
vx2*vx2 + vy2*vy2 == vy*vy
(Here, x1o, y1o, x2o and y2o are coordinates of the initial positions.)
If there is a way to linearize the problem, I don't see it. You can however solve iteratively, and quickly, by the same Newton-Raphson technique GPS uses to work out your position from satellite signals. Of course, to fill in the details and implement this will demand some work!
Update: I think that #Alnitak may have linearized your problem rather neatly. Perhaps a combination of his approach and mine therefore would prosper. (I still think that you'll want to use a Newton-Raphson iteration to converge on #Altinak's T.)

Since the speeds are fixed, this should be solvable using the idea of parallel navigation. Think of it this way. At time 0, there is a line between O1 and O2 (the LOS, or line of sight). If O2 follows the optimal intersect path, then at time 1, the line between O1 and O2 will be parallel to the time 0 LOS. Since you have O2's speed, you can calculate the distance it will travel between time 0 and time 1, and from that can calculate where that intersects the time 1 LOS. Think of scribing a circle around O2's original position with radius equal to the distance it will travel in that interval of time. The intersection(s) of that circle with the second LOS will contain the solution. If there is no intersect, there is no solution. The beginning of this online book has a diagram and formulas that show the concept:
http://www.crcnetbase.com/doi/abs/10.1201/9781420062281.ch2
This problem has real world applications where you may also find this solution talked about. For instance submarines can use this to plot and maintain an intercept course with their target by keeping the LOS bearing to their target constant as they close on their target.
Edit:
This diagram shows what I'm talking about. This can be solved using trigonometry except for the special case where the target O1 is moving directly towards or away from the missile O2 (which can be solved trivially).
In the diagram above we can take some arbitrary small amount of time. During that time t1, O1 will have traveled distance b, and O2 will have traveled distance f. The line between O1 and O2 at time t0 is parallel to the line between O1 and O2 at time t1. Since we are given the initial positions of O1 and O2 we know distance d, and since we are given O1's direction, we can simply calculate the angle A.
So given A, b, f, and d, using the law of Cosines,
a = sqrroot(c^2 + b^2 - (2cb * cos(A)))
and
B = arccos((a^2 + c^2 - b^2)/2ac)
Using the law of Sines
E = arcsin((a * sin(B))/f) or the ambiguous value of 180 - that value
and with that
BC = 180 - E (because C = 180 - B - E so C+B = 180 - E
with BC we have the solution, and the any other aspects of the triangle of the initial locations of O1 and O2 and the intersection point can be similarly calculated.
It's been many years since I used my high school trig, so there may be a simplification of this that I've missed, but this hopefully explains the solution approach I initially described.

Related

Calculate angle of reach for projectile given velocity and distance

I've been trying to implement projectile motion in Javascript and I'm stuck at figuring out the angle (from which to derive the x and y velocity)
var v = 1;
var d = 10;
var g = -1;
var angle = 0.5 * Math.asin((g*d)/(v*v));
I would've expected someting like this to work, since it's coming from here Here.
The values I seem to get are either NaN or a very small number.
To give a bit more context; The projectile has to go from point A to point B (the distance is d in my code) where A and B are at the same height. Later on I would like to randomize the distance and angle a bit, but I assume that's not going to be an issue once this angle problem is solved.
EDIT:
As for a better example:
var v = 100;
var d = 100;
var g = 1; // I've made this positive now
var angle = 0.5 * Math.asin((g*d)/(v*v));
This says that angle is now 0.005
Im not really good at this physics problrm but i'll try
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asin
the arg for Math.asin() should be between -1 and 1. Otherwise it returns NaN
from your example you run Math.asin(-10/1) maybe the velocity has max distance that it could cover. No matter the angle, 1m/s wont reach 500m distance for example
in your link there is a formula to count the max distance from given velocity and angle. Use that to confirm your variables are relevant.
angles are represented in values between -1 to 1 in cos, sin, tan. It makes sense that NaN (or values outside the range) means no angle can cover the distance
Hope it helps a bit
Note from the same wikipedia page you linked d_max = v*v/g. Given your inputs this evaluates to 1. Therefore a distance of 10 is impossible.
Another way to notice this is the range of sin is (-1,1). Therefore asin of any number outside of this range is undefined. (g*d)/(v*v) is 10, so Math.asin returns NaN

formula for changing speed of object moving in circle

I am currently working moving different cars around a race track. I am using the formula listed in
Canvas move object in circle
arccos (1- ( d ⁄ r ) 2 ⁄ 2 )
to vary the speed of the cars around the ends of the track and it works very well. What I don't understand is how the formula is derived. I have been working on trying to derive it from the second derivative of the arcsin or arccos but I can't get out the formula (so am guessing I'm walking the wrong path). Anyways, I am never comfortable using code I don't understand, so I would appreciate it if someone could shed some light on it for me.
As detailed in the linked question, the movement of an object along a circle can be parametrized with a single angle theta which in loose terms describes how many "revolutions" the object has already made. Now, the question is for which angle theta the object is at Euclidean distance d from the initial (current) position A:
In other words, if you fix the time step delta of your simulation, the problem can be restated as to how one should adjust (increment) the angle so that the object displaces within the time interval delta to distance d.
From the law of cosines, one gets:
d^2 = r^2 + r^2 - 2*r*r*cos(theta) = 2*r^2*(1 - cos(theta))
Thus:
cos(theta) = 1 - 1/2*(d/r)^2
theta = arccos(1 - 1/2*(d/r)^2)

how does atan2 work? which angle is actually calculated?

As you can see in the picture, I have a line and two points(p1 and p4). what I need to do is to get snapped point of p1/p4 on the line and then use atan2 to calculate the angle between (p1 and p2) and (p3 and p4). Now, I have two formulas:
var anglep1p2 = Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180 / Math.PI;
var anglep4p3 = Math.atan2(p4[1] - p3[1], p4[0] - p3[0]) * 180 / Math.PI;
anglep1p2 is calculated 103.66797855556482
anglep4p3 is calculated -76.74971541138642
I wonder how does atan2 calculate those values?
thanks for any help
These answers do make sense. You are sort of calculating a the angle of a single line, starting from the positive x-axis. The way you calculate anglep1p2, it corresponds to the line drawn from p1 to p2.
If you plunk the origin of a coordinate system at the starting point p1 (you put it at p2 in your diagram), then the number you get should be the rotation from the positive x-axis to the line you drew - a bit over 90 degrees makes intuitive sense.
Your second result is flipped from your first (notice you used p4/p3 in the same order as your variable name, whereas you reversed this order in the p1/p2 case). To avoid confusion, I'd use the p1/p2 case to gain understanding, then apply it the same way to the other case once you know what you want.
If you have a specific geometry/relationship problem you need to figure out, you can provide the details and I might be able to help more specifically.

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

Finding a rotation around a point

I've managed to get a function working which will calculate and return the angle between one point and another. I've called it the lookAt function, because it's basically causing one transform to look at another one. Here it is:
this.lookAt = function(target) {
var d = target.subtract(this.position)
this.rotation = Math.atan2(d.y, d.x) + Math.PI/2;
return this.rotation;
}
In this function's context, this refers to a surrounding object which has the variables rotation (a rotation in radians) and position, a Vector2 class which has a few basic math functions and stores x and y values. d is a Vector2 created by calling a helper function on the variable target, which subtracts one Vector2 from another.
This works as expected--if I call this function on an object, the rotation correctly "looks at" the target. However, I'd like to know why I had to add π / 2 (which is 1 radian, correct?). I got the original equation from this question, but the answer did not add π / 2 to the equation, whereas I have to.
Could somebody explain the math behind this? Also, I haven't gotten to that much trigonometry yet (besides what my Algebra course introduced me to), so please explain this as if you were talking to a very small child. :-)

Categories

Resources