I need to implement a bit of AI to figure out how to hit a target with projectile motion.
I found this over at wikipedia:
Angle required to hit coordinate
Which looks like just the thing I need, especially since I have the added problem launching the projectile from above zero height. However, my maths skills aren't great so it all looks like complete nonsense to me and I have no idea how to translate any of it into code.
If anyone can break this down into something I can understand with basic operators (+ - * %) and functions (sin, cos, sqrt etc.), I'd really appreciate it.
If xTarget/yTarget is the position of the target, xProj/yProj the initial position of the projectile and v the initial velocity of the projectile (in meters per second), then the formula would translate to the following pseudo code:
x = xTarget - xProj;
y = yTarget - yProj;
g = 9.8;
tmp = pow(v, 4) - g * (g * pow(x, 2) + 2 * y * pow(v, 2));
if tmp < 0
// no solution
else if x == 0
angle1 = pi/2;
if y < 0
angle2 = -pi/2;
else
angle2 = pi/2;
end
else
angle1 = atan((pow(v, 2) + sqrt(tmp)) / (g * x));
angle2 = atan((pow(v, 2) - sqrt(tmp)) / (g * x));
end
g is the graviational constant (~9.8 m/s^2), atan is the arcus tangent function and pow is the power function. The if-statement is necessary, because the formula can have no solution (if the target is not reachable with the initial velocity), one solution (then angle1 == angle2) or two solutions (as can be seen in this animation; this is also why you have the +/- sign in the formula).
In most programming languages you will also find atan2, in which case you should be able to replace some code with
if tmp < 0
// no solution
else
angle1 = atan2(pow(v, 2) + sqrt(tmp), g * x);
angle2 = atan2(pow(v, 2) - sqrt(tmp), g * x);
end
The formula is quite simple, don't worry about the derivation.
x is the horizontal distance away of the target you're trying to hit
y is the vertical distance away of the target you're trying to hit
v is the initial velocity of the launch
g is the acceleration due to gravity (9.81 m/s on earth)
and the formula on that link will give you the angle you need to launch the projectile in order to hit the target on the coordinate (x,y)
Related
I'm trying to write the C code for an algorithm that approximates pi. It's supposed to get the volume of a cube and the volume of a sphere inside that cube (the sphere's radius is 1/2 of the cube's side). Then I am supposed to divide the cube's volume by the sphere's and multiply by 6 to get pi.
It's working but it's doing something weird in the part that is supposed to get the volumes. I figure it's something to do the with delta I chose for the approximations.
With a cube of side 4 instead of giving me a volume of 64 it's giving me 6400. With the sphere instead of 33 it's giving me 3334. something.
Can someone figure it out? Here is the code (I commented the relevant parts):
#include <stdio.h>
int in_esfera(double x, double y, double z, double r_esfera){
double dist = (x-r_esfera)*(x-r_esfera) + (y-r_esfera)*(y-r_esfera) + (z-r_esfera)*(z-r_esfera);
return dist <= (r_esfera)*(r_esfera) ? 1 : 0;
}
double get_pi(double l_cubo){
double r_esfera = l_cubo/2;
double total = 0;
double esfera = 0;
//this is delta, for the precision. If I set it to 1E anything less than -1 the program continues endlessly. Is this normal?
double delta = (1E-1);
for(double x = 0; x < l_cubo; x+=delta){
printf("x => %f; delta => %.6f\n",x,delta);
for(double y = 0; y <l_cubo; y+=delta){
printf("y => %f; delta => %.6f\n",y,delta);
for(double z = 0; z < l_cubo; z+=delta){
printf("z => %f; delta => %.6f\n",z,delta);
total+=delta;
if(in_esfera(x,y,z,r_esfera))
esfera+=delta;
}
}
}
//attempt at fixing this
//esfera/=delta;
//total/=delta;
//
//This printf displays the volumes. Notice how the place of the point is off. If delta isn't a power of 10 the values are completely wrong.
printf("v_sphere = %.8f; v_cube = %.8f\n",esfera,total);
return (esfera)/(total)*6;
}
void teste_pi(){
double l_cubo = 4;
double pi = get_pi(l_cubo);
printf("%.8f\n",pi);
}
int main(){
teste_pi();
}
total+=delta;
if(in_esfera(x,y,z,r_esfera))
esfera+=delta;
total and esfera are three-dimensional volumes whereas delta is a one-dimensional length. If you were tracking units you'd have m3 on the left and m on the right. The units are incompatible.
To fix it, cube delta so that you're conceptually accumulating tiny cubes instead of tiny lines.
total+=delta*delta*delta;
if(in_esfera(x,y,z,r_esfera))
esfera+=delta*delta*delta;
Doing that fixes the output, and also works for any value of delta:
v_sphere = 33.37400000; v_cube = 64.00000000
3.12881250
Note that this algorithm "works" for arbitrary delta values, but it has severe accuracy issues. It's incredibly prone to rounding problems. It works best when delta is a power of two: 1/64.0 is better than 1/100.0, for example:
v_sphere = 33.50365448; v_cube = 64.00000000
3.14096761
Also, if you want your program to run faster get rid of all those printouts! Or at least the ones in the inner loops...
The thing is that multiplication over integers like a * b * c is the same as adding 1 + 1 + 1 + 1 + ... + 1 a * b * c times, right?
You're adding delta + delta + ... (x / delta) * (y / delta) * (z / delta) times. Or, in other words, (x * y * z) / (delta ** 3) times.
Now, that sum of deltas is the same as this:
delta * (1 + 1 + 1 + 1 + ...)
^^^^^^^^^^^^^^^^^^^^ (x * y * z) / (delta**3) times
So, if delta is a power of 10, (x * y * z) / (delta**3) will be an integer, and it'll be equal to the sum of 1's in parentheses (because it's the same as the product x * y * (z / (delta**3)), where the last term is an integer - see the very first sentence of this answer). Thus, your result will be the following:
delta * ( (x * y * z) / (delta ** 3) ) == (x * y * z) / (delta**2)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the sum of ones
That's how you ended up calculating the product divided by delta squared.
To solve this, multiply all volumes by delta * delta.
However, I don't think it's possible to use this logic for deltas that aren't a power of 10. And indeed, the code will go all kinds of haywire for delta == 0.21 and l_cubo == 2, for example: you'll get 9.261000000000061 instead of 8.
i have two Objects in a 3D World and want to make the one object facing the other object. I already calculated all the angles and stuff (pitch angle and yaw angle).
The problem is i have no functions to set the yaw or pitch individually which means that i have to do it by a quaternion. As the only function i have is: SetEnetyQuaternion(float x, float y, float z, float w). This is my pseudocode i have yet:
float px, py, pz;
float tx, ty, tz;
float distance;
GetEnetyCoordinates(ObjectMe, &px, &py, &pz);
GetEnetyCoordinates(TargetObject, &tx, &ty, &tz);
float yaw, pitch;
float deltaX, deltaY, deltaZ;
deltaX = tx - px;
deltaY = ty - py;
deltaZ = tz - pz;
float hyp = SQRT((deltaX*deltaX) + (deltaY*deltaY) + (deltaZ*deltaZ));
yaw = (ATAN2(deltaY, deltaX));
if(yaw < 0) { yaw += 360; }
pitch = ATAN2(-deltaZ, hyp);
if (pitch < 0) { pitch += 360; }
//here is the part where i need to do a calculation to convert the angles
SetEnetyQuaternion(ObjectMe, pitch, 0, yaw, 0);
What i tried yet was calculating the sinus from those angles devided with 2 but this didnt work - i think this is for euler angles or something like that but didnt help me. The roll(y axis) and the w argument can be left out i think as i dont want my object to have a roll. Thats why i put 0 in.
If anyone has any idea i would really appreciate help.
Thank you in advance :)
Let's suppose that the quaternion you want describes the attitude of the player relative to some reference attitude. It is then essential to know what the reference attitude is.
Moreover, you need to understand that an object's attitude comprises more than just its facing -- it also comprises the object's orientation around that facing. For example, imagine the player facing directly in the positive x direction of the position coordinate system. This affords many different attitudes, from the one where the player is standing straight up to ones where he is horizontal on either his left or right side, to one where he is standing on his head, and all those in between.
Let's suppose that the appropriate reference attitude is the one facing parallel to the positive x direction, and with "up" parallel to the positive z direction (we'll call this "vertical"). Let's also suppose that among the attitudes in which the player is facing the target, you want the one having "up" most nearly vertical. We can imagine the wanted attitude change being performed in two steps: a rotation about the coordinate y axis followed by a rotation about the coordinate z axis. We can write a unit quaternion for each of these, and the desired quaternion for the overall rotation is the Hamilton product of these quaternions.
The quaternion for a rotation of angle θ around the unit vector described by coordinates (x, y, z) is (cos θ/2, x sin θ/2, y sin θ/2, z sin θ/2). Consider then, the first quaternion you want, corresponding to the pitch. You have
double semiRadius = sqrt(deltaX * deltaX + deltaY * deltaY);
double cosPitch = semiRadius / hyp;
double sinPitch = deltaZ / hyp; // but note that we don't actually need this
. But you need the sine and cosine of half that angle. The half-angle formulae come in handy here:
double sinHalfPitch = sqrt((1 - cosPitch) / 2) * ((deltaZ < 0) ? -1 : 1);
double cosHalfPitch = sqrt((1 + cosPitch) / 2);
The cosine will always be nonnegative because the pitch angle must be in the first or fourth quadrant; the sine will be positive if the object is above the player, or negative if it is below. With all that being done, the first quaternion is
(cosHalfPitch, 0, sinHalfPitch, 0)
Similar analysis applies to the second quaternion. The cosine and sine of the full rotation angle are
double cosYaw = deltaX / semiRadius;
double sinYaw = deltaY / semiRadius; // again, we don't actually need this
We can again apply the half-angle formulae, but now we need to account for the full angle to be in any quadrant. The half angle, however, can be only in quadrant 1 or 2, so its sine is necessarily non-negative:
double sinHalfYaw = sqrt((1 - cosYaw) / 2);
double cosHalfYaw = sqrt((1 + cosYaw) / 2) * ((deltaY < 0) ? -1 : 1);
That gives us an overall second quaternion of
(cosHalfYaw, 0, 0, sinHalfYaw)
The quaternion you want is the Hamilton product of these two, and you must take care to compute it with the correct operand order (qYaw * qPitch), because the Hamilton product is not commutative. All the zeroes in the two factors make the overall expression much simpler than it otherwise would be, however:
(cosHalfYaw * cosHalfPitch,
-sinHalfYaw * sinHalfPitch,
cosHalfYaw * sinHalfPitch,
sinHalfYaw * cosHalfPitch)
At this point I remind you that we started with an assumption about the reference attitude for the quaternion system, and the this result depends on that choice. I also remind you that I made an assumption about the wanted attitude, and that also affects this result.
Finally, I observe that this approach breaks down where the target object is very nearly directly above or directly below the player (corresponding to semiRadius taking a value very near zero) and where the player is very nearly on top of the target (corresponding to hyp taking a value very near zero). There is a non-zero chance of causing a division by zero if you use these formulae exactly as given, so you'll want to think about how to deal with that.)
I have been working for too long on the business applications it seems... And graduated too long ago perhaps :) Recently i have been tasked writing a small simulation of robotics using C (although this question is more math/algorithm than C) where i have two units (tank-bots) starting at an X and Y coordinate on a playfield.
Now there are keys on a panel to rotate them, and a key to move them forwards. I am now facing a minor brain meltdown on the translation from degrees of rotation to the next X,Y coord to move to in the cartesian playfield.
Due to limitations in HW only fixed point is available for the actual movement, but calculations can be done by float values.
I wrote the following code just from memory just now:
/* Recalculate to radians */
int radians;
/* Use sin and cos to get a vector (new x and y coords). Translate from polar to
cartesian coordinates */
radians = (int) _tanks[0].rotationAngle * (M_PI / 180);
_tanks[0].x += _tanks[0].speed * cos(radians);
_tanks[0].y += _tanks[0].speed * sin(radians);
radians = (int) _tanks[1].rotationAngle * (M_PI / 180);
_tanks[1].x += _tanks[1].speed * cos(radians);
_tanks[1].y += _tanks[1].speed * sin(radians);
Unfortunately it seems my brain is not really refreshed on polar coordinate math and geometry after all these years on writing pure biz software, so it seems to not work as intended.
For instance if rotationAngle is 180, instead the next x/y is to the left, causing the bot to topple over :)
What i want is a movement scheme similiar to the old Micro Machines games if you remember where the next point would be in front of where the object is facing, so it moves (speed) number of steps there.
Can someone suggest where i'm going wrong here...
Also, if there is a smoother way of doing this in C than the pure mathematical attempt that i just wrote (badly at that), give me a hint.
EDIT:
Tried to add :
float radians;
radians = (45 - _tanks[0].rotationAngle) * (M_PI / 180);
_tanks[0].x += (int) (_tanks[0].speed * cos(radians));
_tanks[0].y += (int) (_tanks[0].speed * sin(radians));
according to answer below as 0 degrees is indeed the positive Y-axis. But this also gives incorrect results. Now movement for 180 degrees starting point is upwards to the left. At 180 degrees should be movement along the negative Y axis.
Some more code:
Init of _tank struct -
tanks[0].acc = 0;
tanks[0].dec = 0;
tanks[0].rotationAngle = 180;
tanks[0].speed = 0;
tanks[0].x = 400;
tanks[0].y = 150;
tanks[0].turretRotationAngle = 180;
The rotation degree is just a number (fixed integer), and i wrap it around as according to the circle # 360 degrees, like so -
switch(direction) {
case 0:
tank->rotationAngle -= degrees;
if(tank->rotationAngle < 1) {
tank->rotationAngle = 360;
}
break;
case 1:
tank->rotationAngle += degrees;
if(tank->rotationAngle > 360) {
tank->rotationAngle = 0;
}
break;
}
One key for rotating clockwise, one for counter clockwise.
The rotation works, but the movement does not, as described...
Results of debug run:
Initial state (no movement due to 0 speed) -
radians = -2.3561945
x = 400
y = 150
speed = 0
After movement (speed > 0) -
radians = -2.3561945 (the same since i only press the move button)
x = 399
y = 149
speed = 2
This seems odd. The X coord should not change at all if the rotation is 180 degrees from the initial origin right? Only the Y should change, and in the opposite direction. I would translate the change instead to if speed is 2, vector length should be 2 so change would be 2 steps in the direction the object is facing, so y = y + 2 and x = x + 0 for 180 degree rotation on the object?
I feel like i'm getting there :)
Further EDIT:
Seems to be ALMOST correct along the lines of what i need for the playfield if i do this:
radians = (_tanks[0].rotationAngle - 90) * (M_PI / 180);
Note -90...
Still when speed is lowered it seems to glitch but at least it moves in the right direction.
For instance if rotationAngle is 180, instead the next x/y is to the left, causing the bot to topple over :)
Yes, that is what your code does: your code is correct, aside from the int radians issue that user3386109 mentions above, provided that 0° is the positive x-axis, 90° is the positive y-axis, 180° is the negative x-axis, 270° (or -90°) is the negative y-axis.
I'm guessing that instead, you want 0° to be the positive y-axis? And — do you want 90° to be the positive x-axis (so your angles proceed clockwise around the circle), or the negative x-axis (so they proceed counterclockwise)? For the former (clockwise) case, just change _tanks[...].rotationAngle to (90 - _tanks[...].rotationAngle) (to "flip" around the 45° line); for the latter (counterclockwise) case, just change it to (_tanks[...].rotationAngle + 90) (to "rotate" it 90° about the origin).
#ruakh and #user3386109 well discuss the issues about angle units and phase.
In addtion, for a "smoother way of doing this in C" also consider:
Use round(), else code will introduce a bias. (Assuming _tanks[1].x is some integer)
double radians = _tanks[0].rotationAngle * (M_PI / 180);
_tanks[0].x += (int) round(_tanks[0].speed * cos(radians));
Use float rather than double as the extra precision with its longer calculation time are not needed.
float radians = _tanks[0].rotationAngle * (float)((M_PI / 180));
_tanks[0].x += (int) roundf(_tanks[0].speed * cosf(radians)); // note function names
If processing time is limited, an integer look-up-table could be used with 360 int scaled sine and cosine values rather than all the floating point math.
_tanks[0].x += (_tanks[0].speed * LUT_cos[_tanks[0].rotationAngle])/scale;
I want to animate a sine wave as if it's generated from the center and moves outwards (to the left and right).
I started with this library: SISinusWaveView and made some adjustments. Currently I have this naive code for calculating the Y position of the curve depending on the X, phase and frequency:
float width = view.width; float mid = width / 2;
float adjustedX = x < mid ? x : width - x;
float y = maxAmplitude * sinf(2 * M_PI *(adjustedX / mid) * frequency + phase);
// phase increases every frame
Quite obviously, this causes a sharp angle at the middle of the sine wave, as seen below:
I would like to make it so the horizontal center of the animation is a smooth curve rather than a sharp angle, while keeping the animation horizontally symmetric. How would I approach this? Any mathematical insight for achieving this is appreciated.
EDIT
I tried to implement #TheBigH suggestion, but the parabola section is not seamlessly continuing the sine curve. Here's what I tried (implemented on Mathematica for quick visualization):
amp = 10;
freq = 1.5;
phase = 0.5;
Z = 1;
plotSine = Plot[amp*Sin[freq*x + phase], {x, Z, 2 Pi}];
aPara = amp*freq*Cos[phase]/(2 Z);
bPara = 0;
cPara = amp*Sin[c] - aPara*Z^2;
plotPara =
Plot[aPara*x^2 + bPara*x + cPara, {x, -Z, Z },
PlotRange -> {{-Z, Z}, {-20, 20}}];
Show[plotPara, plotSine, PlotRange -> {{-2 Pi, 2 Pi}, {-20, 20}}
Which results in this:
Changing the sign of the parabola didn't quite work either:
EDIT 2
I see now that the problem was assuming s(0) = p(Z) and s'(0) = p'(Z); instead of s(z) = p(Z) and s'(Z) = p'(Z). Moving the sine wave so it would start exactly at the end of the parabole would fix the problem, but it's more convenient solving the parabola such as s(z) = p(Z) and s'(Z) = p'(Z) as that would simplify the implementation. How to do this?
EDIT 3
See this math.stackexchange.com answer for the final solution.
Since OP has asked me to elaborate, here's my take as an answer.
Most generally, you're plotting the function s(x) = a sin(bx + c), where a, b and c come from the original problem. Later we will shift the sine curve by some offset Z but I'll leave it out of the sine curve for now as it will complicate the mathematics.
The new parabolic section will have equation p(x) = Ax^2 + Bx + C (A, B and C are different variables than a,b and c).
You want the two equations to join up cleanly, which means s(0) = p(Z). You also want the slopes to join up nicely so that there are no corners. That means you also need s'(0) = p'(Z). Also, since the parabola is centered about the origin, B = 0.
Thus you have two simultaneous equations for A, C given that you already know a, b, c and Z
a sin( c ) = A Z^2 + C
ab cos( c ) = 2AZ
or
A = ab cos( c ) / (2Z)
C = a sin (c) - A Z^2
This gives you the equation of the parabola, which you plot between -Z and Z. Then all you have to do is plot the sine curves, now adding in that offset. Let me know if any of this is unclear.
EDIT: I see there is a vertical shift of the sine wave as well. This does not pose any problems; just leave it out to begin with and add it to the parabola and sine waves at the end.
I have the coordinates (x,y) of 2 points. I want to build the third point so that these 3 points make an equilateral triangle.
How can I calculate the third point?
Thank you
After reading the posts (specially vkit's) I produced this simple piece of code which will do the trick for one direction (remember that there are two points). The modification for the other case shold be trivial.
#include<stdio.h>
#include<math.h>
typedef struct{
double x;
double y;
} Point;
Point vertex(Point p1, Point p2){
double s60 = sin(60 * M_PI / 180.0);
double c60 = cos(60 * M_PI / 180.0);
Point v = {
c60 * (p1.x - p2.x) - s60 * (p1.y - p2.y) + p2.x,
s60 * (p1.x - p2.x) + c60 * (p1.y - p2.y) + p2.y
};
return v;
}
You could rotate the second point 60° around first to find the location of the third point.
Something like this:
//find offset from point 1 to 2
dX = x2 - x1;
dY = y2 - y1;
//rotate and add to point 1 to find point 3
x3 = (cos(60°) * dX - sin(60°) * dY) + x1;
y3 = (sin(60°) * dX + cos(60°) * dY) + y1;
Let's call your two points A and B. Bisect AB, call this point C. Find the slope of AB (YA-YB / XA-XB), call it m. Find the perpendicular to that (-1/m) and call it m2. Then compute a segment CD whose length is sin(60) * length(AB), at the slope m2 (there will be two such points, one to each side of AB). ABD is then your equilateral triangle.
That, obviously, is a "constructive" method. You should also be able to do it by solving a set of linear equations. I haven't tried to figure out the right system of equations for this case, but this approach tends to be somewhat more stable numerically, and has fewer special cases (e.g., with the constructive version, a slope of 0 has to be dealt with specially).
For BlueRaja's challenge go to end of post:
Answer using translation and rotation:
Says points are P(x1,y1) and Q(x2,y2).
Since it is graphics, you can use tranforms to get the point.
First translate axes so P is the origin.
Next rotate Q around P by 60 degrees (or -60 to get the other possible point).
This gives you the coordinates of the third point say R, when P is the origin.
Translate back and there you have it.
You can use standard graphics API which take care of precision etc issues for you. No headaches.
Of course you could do the math and actually come up with a formula and use that and that might be faster, but then the question might get closed as off-topic ;-)
To take up BlueRaja's challenge: Here is a method which does not use trigonometry.
Given points P(x1,y1) and Q(x2,y2)
Say the point we need (R) to find is (x3,y3).
Let T be midpoint of PQ.
We know the area of triangle PQR (as it is equilateral and we know the side)
and we know the area of triangle PRT (1/2 the earlier area).
Now area of a triangle can be written as a determinant having the co-ordinates as entries:
2*Area = |D|
where
| 1 x1 y1|
D = | 1 x2 y2|
| 1 x3 y3|
We have two such equations (which are linear), solve for x3 and y3.
pc <- c((x1+x2)/2,(y1+y2)/2) #center point
ov <- c(y2-y1,x1-x2) #orthogonal vector
p3 <- pc+sqrt(3/4)*ov #The 3dr point in equilateral triangle (center point + height of triangle*orthogonal vector)