Within a 2D infinite space, I have two circular sprites that represent massive bodies in outer space.
Each body has a pair of x, y coordinates, a mass, a speed and a direction (in radians).
On each frame of animation, I'm running the following code for each body (where this is the body being updated, and other is the other body):
x, y = other.x - this.x, other.y - this.y
angle = atan2(y, x)
distance = root(square(x) + square(y))
force = this.mass * other.mass / square(distance)
Note: I'm ignoring G as it's just a multiplier.
I know how to move the bodies around, based on their coordinates, speed and direction, but do not know how to update this.speed and this.direction to simulate gravity.
The gravitational force acting on a given body is represented as a vector and produces acceleration with components (ax and ay), which are calculated (based on what you already have) like this:
squared_distance = square(x) + square(y)
distance = sqrt(squared_distance)
accel = other.mass / squared_distance
ax = accel * x / distance
ay = accel * y / distance
Note that the angle (force/acceleration direction) is not needed.
Each body should have an associated velocity (instead of speed), which should be a two-component vector (vx and vy). It is updated like this (where dt is the time interval between updates):
this.vx += ax * dt
this.vy += ay * dt
Once the velocity of a given body has been updated, it can then be repositioned (updating its x, y coordinates) like this:
this.x += this.vx * dt
this.y += this.vy * dt
You can calculate the speed and direction if you need them, but they are not needed here.
Related
I have a custom Dougnut chart drawer, which can draw doughnuts like this:
I'm pretty satisfied with this, however I'd like to finetune it and I just have no idea of the right solution.
I'd like to have parallel gaps (paddings) between slices.
I raised those paddings to have better view of my goal.
This is how I'm drawing this currently:
double cx, cy; //center points of circle
double r1, r2; //radius of outer and inner circle
double pad = M_PI / 360 * 12; //12 degree pad
double alpha = -1 * M_PI / 2; //starting from up (noon)
double da = 0.0; // delta of current slice
for (ALL_SLICES) {
calculate_da(&da); //calculate slice's delta
// drawing
move_pen(cx + r2 * cos(alpha + pad/2), cy + r2 * sin(alpha + pad/2)); //STEP1
draw_arc(cx, cy, r1, alpha + pad/2, alpha + da - pad/2); //STEP2
draw_negative_arc(cx, cy, r2, alpha + da - pad/2, alpha + pad/2); //STEP3
fill();
//update next alpha
alpha += da;
}
Drawing steps:
So this is fairly simple, however to have parallel gaps between slices, I'd have to calculate another angle for the outer end and start points:
I've drawn quite a lot of triangles already but I was not able to solve this easily.
Again: the goal is to have parallel paddings between the slices, i.e. bringing these 2 red dots closer together on the circumference, to get exact parallel lines between two slices.
Update:
This is how it looks with scaled padding from #chux answer:
Unfortunately it's not perfect, these lines are not parallel:
"Pad should be 12degree" --> If you want parallel lines, the inner padding angle and outer padding angles are different - not both 12°.
Use a scaled padding.
Given the outer padding angle is double pad = ...,
Draw first arc as before.
draw_arc(cx, cy, r1, alpha + pad/2, alpha + da - pad/2); //STEP2
The inner padding angle is proportionally larger.
double inner_pad = pad*r1/r2;
draw_negative_arc(cx, cy, r2, alpha + da - inner_pad/2, alpha + inner_pad/2); //STEP3
Tip: Less confusing to use r_inner, r_outer, than r2, r1.
Minor: Padding calculation looks off by 2x.
// double pad = M_PI / 360 * 12; //12 degree pad.
double pad = 2 * M_PI / 360 * 12; //12 degree pad.
We'll solve this for the case of one arbitrary gap. Then, you can apply our solution to any number of gaps.
First, we can calculate the average of the angles at which the two boundaries radiate from the center. This will be the angle at which our two new segments will radiate from the inner circle to the outer circle. If the original segments radiate from the center of the inner circle at angles a and b, let c be the angle going through the middle of the smaller segment of the circle between them. If a = 60 deg and b = 90 deg, choose c = 75 deg as an example.
Now, for the points of intersection with the edge of the inner circle you've already found, place lines whose slopes have angle c (w.r.t. the positive x-axis, as normal). Then, find the correspondibg points of intersection with the outer circle. These new outer points and your old inner points define the parallel segments that you are looking for.
Example: inner circle radius r = 10; outer circle radius 20; angles a = 30 deg and b = 60 deg. Your current inner and outer points are p1 = (5sqrt(3), 5), p2 = (5, 5sqrt(3)), q1 = 2p1, q2 = 2p2 (assume the center of the inner circle is at the origin here). Calculate c = 45 deg. The slope of lines with this angle can be found using tangent; m = 1. Define two lines going through p1 and p2 with slope 1; we get y = x + 5(1 - sqrt(3)) and y = x + 5(sqrt(3) - 1). Now we find the points of intersection of these lines with the outer circle; I will leave this as an exercise but basically just take the equation of the outer circle, x^2 + y^2 = 400, replace y in this with the right-hand side of each equation, and solve for x. You will get two solutions (a line passing through a point inside the outer circle must intersect the circle in two places)... pick the one the is in the smaller segment defined by your original outer points.
This seems pretty tedious to do by hand, and it is, but in reality once you write the code once the computer will have no issue doing this for you all day long.
(Please comment if you need me to try to work out the points of intersection in the example, on my phone now).
For #chux - Reinstate Monica's request, I'm posting here my final solution which relies heavily on his link in comment: Radius and central angle on a circular segment.
1.) Set the initial constants:
double pad = PADDING_VALUE;
double r_inner = INNER_RADIUS;
double r_outer = OUTER_RADIUS;
2.) Calculate the slice's starting point's coordinates (on the inner circle):
double alpha = CURRENT_SLICE_ANGLE;
double inner_angle = alpha + pad/2; // slice will begin with pad/2 offset
double x1 = r_inner * cos(inner_angle);
double y1 = r_inner * sin(inner_angle);
3.) Now calculate the previous slice's ending point's coordinates (also on the inner circle):
double x2 = r_inner * cos(inner_angle - pad); //easy, exactly angle-pad
double y2 = r_inner * sin(inner_angle - pad); //easy, exactly angle-pad
4.) Now I have the coords for the current slice's start and the previous slice's end. I want to keep a constant length between the 2 slices, and this length should be exactly the segment's length between (x1,y1) and (x2,y2). This is a right-angled triangle, and the segment's length I'm looking for is easily expressable with Pitagoras formula:
double hyp = sqrt(pow(x1-x2,2) + pow(y1-y2,2));
5.) This hyp is exactly the chord length (c) here:
6.) From the same Wikipedia page the Theta is expressible with chord length and radius:
double theta = 2 * asin(hyp / 2 / r_outer);
7.) I have to draw the outer arc with the help of theta and pad:
double outer_angle_correction = (pad - theta) / 2;
Applying this calculation results this:
This is a bit odd, as the gaps are a bit too large. But anyway these huge gaps were only used for demonstration, and after I changed them back to my initial intended values this is the result:
Perfectly parallel gaps between all slices without using any approximation - just pure math. Sweet.
I'm currently working on a game project and need to render a point in front of the current players vision, the game is written in a custom c++ engine. I have the current position (x,y,z) and the current rotation (pitch,yaw,roll). I need to extend the point forward along the known angle at a set distance.
edit:
What I Used As A Solution (Its slightly off but that's ok for me)
Vec3 LocalPos = {0,0,0};
Vec3 CurrentLocalAngle = {0,0,0};
float len = 0.1f;
float pitch = CurrentLocalAngle.x * (M_PI / 180);
float yaw = CurrentLocalAngle.y * (M_PI / 180);
float sp = sinf(pitch);
float cp = cosf(pitch);
float sy = sinf(yaw);
float cy = cosf(yaw);
Vec3 dir = { cp * cy, cp * sy, -sp };
LocalPos = { LocalPos.x + dir.x * len, LocalPos.y + dir.y * len,LocalPos.z + dir.z * len };
You can get the forward vector of the player from matrix column 3 if it is column based, then you multiply its normal by the distance you want then add the result to the player position you will get the point you need.
Convert the angle to a directional vector or just get the "forward vector" from the player if it's available in the engine you're using (it should be the same thing).
Directional vectors are normalized by nature (they have distance = 1), so you can just multiply them by the desired distance to get the desired offset. Multiply this vector by the distance you want the point to be relative to the reference point (the player's camera vector I presume), and then you just add one to the other to get the point in the world where this point belongs.
Making a game using Golang since it seems to work quite well for games. I made the player face the mouse always, but wanted a turn rate to make certain characters turn slower than others. Here is how it calculates the turn circle:
func (p *player) handleTurn(win pixelgl.Window, dt float64) {
mouseRad := math.Atan2(p.pos.Y-win.MousePosition().Y, win.MousePosition().X-p.pos.X) // the angle the player needs to turn to face the mouse
if mouseRad > p.rotateRad-(p.turnSpeed*dt) {
p.rotateRad += p.turnSpeed * dt
} else if mouseRad < p.rotateRad+(p.turnSpeed*dt) {
p.rotateRad -= p.turnSpeed * dt
}
}
The mouseRad being the radians for the turn to face the mouse, and I'm just adding the turn rate [in this case, 2].
What's happening is when the mouse reaches the left side and crosses the center y axis, the radian angle goes from -pi to pi or vice-versa. This causes the player to do a full 360.
What is a proper way to fix this? I've tried making the angle an absolute value and it only made it occur at pi and 0 [left and right side of the square at the center y axis].
I have attached a gif of the problem to give better visualization.
Basic summarization:
Player slowly rotates to follow mouse, but when the angle reaches pi, it changes polarity which causes the player to do a 360 [counts all the back to the opposite polarity angle].
Edit:
dt is delta time, just for proper frame-decoupled changes in movement obviously
p.rotateRad starts at 0 and is a float64.
Github repo temporarily: here
You need this library to build it! [go get it]
Note beforehand: I downloaded your example repo and applied my change on it, and it worked flawlessly. Here's a recording of it:
(for reference, GIF recorded with byzanz)
An easy and simple solution would be to not compare the angles (mouseRad and the changed p.rotateRad), but rather calculate and "normalize" the difference so it's in the range of -Pi..Pi. And then you can decide which way to turn based on the sign of the difference (negative or positive).
"Normalizing" an angle can be achieved by adding / subtracting 2*Pi until it falls in the -Pi..Pi range. Adding / subtracting 2*Pi won't change the angle, as 2*Pi is exactly a full circle.
This is a simple normalizer function:
func normalize(x float64) float64 {
for ; x < -math.Pi; x += 2 * math.Pi {
}
for ; x > math.Pi; x -= 2 * math.Pi {
}
return x
}
And use it in your handleTurn() like this:
func (p *player) handleTurn(win pixelglWindow, dt float64) {
// the angle the player needs to turn to face the mouse:
mouseRad := math.Atan2(p.pos.Y-win.MousePosition().Y,
win.MousePosition().X-p.pos.X)
if normalize(mouseRad-p.rotateRad-(p.turnSpeed*dt)) > 0 {
p.rotateRad += p.turnSpeed * dt
} else if normalize(mouseRad-p.rotateRad+(p.turnSpeed*dt)) < 0 {
p.rotateRad -= p.turnSpeed * dt
}
}
You can play with it in this working Go Playground demo.
Note that if you store your angles normalized (being in the range -Pi..Pi), the loops in the normalize() function will have at most 1 iteration, so that's gonna be really fast. Obviously you don't want to store angles like 100*Pi + 0.1 as that is identical to 0.1. normalize() would produce correct result with both of these input angles, while the loops in case of the former would have 50 iterations, in the case of the latter would have 0 iterations.
Also note that normalize() could be optimized for "big" angles by using floating operations analogue to integer division and remainder, but if you stick to normalized or "small" angles, this version is actually faster.
Preface: this answer assumes some knowledge of linear algebra, trigonometry, and rotations/transformations.
Your problem stems from the usage of rotation angles. Due to the discontinuous nature of the inverse trigonometric functions, it is quite difficult (if not outright impossible) to eliminate "jumps" in the value of the functions for relatively close inputs. Specifically, when x < 0, atan2(+0, x) = +pi (where +0 is a positive number very close to zero), but atan2(-0, x) = -pi. This is exactly why you experience the difference of 2 * pi which causes your problem.
Because of this, it is often better to work directly with vectors, rotation matrices and/or quaternions. They use angles as arguments to trigonometric functions, which are continuous and eliminate any discontinuities. In our case, spherical linear interpolation (slerp) should do the trick.
Since your code measures the angle formed by the relative position of the mouse to the absolute rotation of the object, our goal boils down to rotating the object such that the local axis (1, 0) (= (cos rotateRad, sin rotateRad) in world space) points towards the mouse. In effect, we have to rotate the object such that (cos p.rotateRad, sin p.rotateRad) equals (win.MousePosition().Y - p.pos.Y, win.MousePosition().X - p.pos.X).normalized.
How does slerp come into play here? Considering the above statement, we simply have to slerp geometrically from (cos p.rotateRad, sin p.rotateRad) (represented by current) to (win.MousePosition().Y - p.pos.Y, win.MousePosition().X - p.pos.X).normalized (represented by target) by an appropriate parameter which will be determined by the rotation speed.
Now that we have laid out the groundwork, we can move on to actually calculating the new rotation. According to the slerp formula,
slerp(p0, p1; t) = p0 * sin(A * (1-t)) / sin A + p1 * sin (A * t) / sin A
Where A is the angle between unit vectors p0 and p1, or cos A = dot(p0, p1).
In our case, p0 == current and p1 == target. The only thing that remains is the calculation of the parameter t, which can also be considered as the fraction of the angle to slerp through. Since we know that we are going to rotate by an angle p.turnSpeed * dt at every time step, t = p.turnSpeed * dt / A. After substituting the value of t, our slerp formula becomes
p0 * sin(A - p.turnSpeed * dt) / sin A + p1 * sin (p.turnSpeed * dt) / sin A
To avoid having to calculate A using acos, we can use the compound angle formula for sin to simplify this further. Note that the result of the slerp operation is stored in result.
result = p0 * (cos(p.turnSpeed * dt) - sin(p.turnSpeed * dt) * cos A / sin A) + p1 * sin(p.turnSpeed * dt) / sin A
We now have everything we need to calculate result. As noted before, cos A = dot(p0, p1). Similarly, sin A = abs(cross(p0, p1)), where cross(a, b) = a.X * b.Y - a.Y * b.X.
Now comes the problem of actually finding the rotation from result. Note that result = (cos newRotation, sin newRotation). There are two possibilities:
Directly calculate rotateRad by p.rotateRad = atan2(result.Y, result.X), or
If you have access to the 2D rotation matrix, simply replace the rotation matrix with the matrix
|result.X -result.Y|
|result.Y result.X|
I have two vectors in a game. One vector is the player, one vector is an object. I also have a vector that specifies the direction the player if facing. The direction vector has no z part. It is a point that has a magnitude of 1 placed somewhere around the origin.
I want to calculate the angle between the direction the soldier is currently facing and the object, so I can correctly pan some audio (stereo only).
The diagram below describes my problem. I want to calculate the angle between the two dashed lines. One dashed line connects the player and the object, and the other is a line representing the direction the player is facing from the point the player is at.
At the moment, I am doing this (assume player, object and direction are all vectors with 3 points, x, y and z):
Vector3d v1 = direction;
Vector3d v2 = object - player;
v1.normalise();
v2.normalise();
float angle = acos(dotProduct(v1, v2));
But it seems to give me incorrect results. Any advice?
Test of code:
Vector3d soldier = Vector3d(1.f, 1.f, 0.f);
Vector3d object = Vector3d(1.f, -1.f, 0.f);
Vector3d dir = Vector3d(1.f, 0.f, 0.f);
Vector3d v1 = dir;
Vector3d v2 = object - soldier;
long steps = 360;
for (long step = 0; step < steps; step++) {
float rad = (float)step * (M_PI / 180.f);
v1.x = cosf(rad);
v1.y = sinf(rad);
v1.normalise();
float dx = dotProduct(v2, v1);
float dy = dotProduct(v2, soldier);
float vangle = atan2(dx, dy);
}
You shoud always use atan2 when computing angular deltas, and then normalize.
The reason is that for example acos is a function with domain -1...1; even normalizing if the input absolute value (because of approximations) gets bigger than 1 the function will fail even if it's clear that in such a case you would have liked an angle of 0 or PI instead. Also acos cannot measure the full range -PI..PI and you'd need to use explicitly sign tests to find the correct quadrant.
Instead atan2 only singularity is at (0, 0) (where of course it doesn't make sense to compute an angle) and its codomain is the full circle -PI...PI.
Here is an example in C++
// Absolute angle 1
double a1 = atan2(object.y - player.y, object.x - player.x);
// Absolute angle 2
double a2 = atan2(direction.y, direction.x);
// Relative angle
double rel_angle = a1 - a2;
// Normalize to -PI .. +PI
rel_angle -= floor((rel_angle + PI)/(2*PI)) * (2*PI) - PI;
In the case of a general 3d orientation you need two orthogonal directions, e.g. the vector of where the nose is pointing to and the vector to where your right ear is.
In that case the formulas are just slightly more complex, but simpler if you have the dot product handy:
// I'm assuming that '*' is defined as the dot product
// between two vectors: x1*x2 + y1*y2 + z1*z2
double dx = (object - player) * nose_direction;
double dy = (object - player) * right_ear_direction;
double angle = atan2(dx, dy); // Already in -PI ... PI range
In 3D space, you also need to compute the axis:
Vector3d axis = normalise(crossProduct(normalise(v1), normalise(v2)));
I was in need of a little math help that I can't seem to find the answer to, any links to documentation would be greatly appreciated.
Heres my situation, I have no idea where I am in this maze, but I need to move around and find my way back to the start. I was thinking of implementing a waypoint list of places i've been offset from my start at 0,0. This is a 2D cartesian plane.
I've been given 2 properties, my translation speed from 0-1 and my rotation speed from -1 to 1. -1 is very left and +1 is very right. These are speed and not angles so thats where my problem lies. If I'm given 0 as a translation speed and 0.2 I will continually turn to my right at a slow speed.
How do I figure out the offsets given these 2 variables? I can store it every time I take a 'step'.
I just need to figure out the offsets in x and y terms given the translations and rotation speeds. And the rotation to get to those points.
Any help is appreciated.
Your question is unclear on a couple of points, so I have to make some assumptions:
During each time interval, translation speed and rotational velocity are constant.
You know the values of these variables in every time interval (and you know rotational velocity in usable units, like radians per second, not just "very left").
You know initial heading.
You can maintain enough precision that roundoff error is not a problem.
Given that, there is an exact solution. First the easy part:
delta_angle = omega * delta_t
Where omega is the angular velocity. The distance traveled (maybe along a curve) is
dist = speed * delta_t
and the radius of the curve is
radius = dist / delta_angle
(This gets huge when angular velocity is near zero-- we'll deal with that in a moment.) If angle (at the beginning of the interval) is zero, defined as pointing in the +x direction, then the translation in the interval is easy, and we'll call it deta_x_0 and delta_y_0:
delta_x_0 = radius * sin(delta_angle)
delta_y_0 = radius * (1 - cos(delta_angle))
Since we want to be able to deal with very small delta_angle and very large radius, we'll expand sin and cos, and use this only when angular velocity is close to zero:
dx0 = r * sin(da) = (dist/da) * [ da - da^3/3! + da^5/5! - ...]
= dist * [ 1 - da^2/3! + da^4/5! - ...]
dy0 = r * (1-cos(da)) = (dist/da) * [ da^2/2! - da^4/4! + da^6/6! - ...]
= dist * [ da/2! - da^3/4! + da^5/6! - ...]
But angle generally isn't equal to zero, so we have to rotate these displacements:
dx = cos(angle) * dx0 - sin(angle) * dy0
dy = sin(angle) * dx0 - cos(angle) * dy0
You could do it in two stages. First work out the change of direction to get a new direction vector and then secondly work out the new position using this new direction. Something like
angle = angle + omega * delta_t;
const double d_x = cos( angle );
const double d_y = sin( angle );
x = x + d_x * delta_t * v;
y = y + d_y * delta_t * v;
where you store your current angle out at each step. ( d_x, d_y ) is the current direction vector and omega is the rotation speed that you have. delta_t is obviously your timestep and v is your speed.
This may be too naive to split it up into two distinct stages. I'm not sure I haven't really thought it through too much and haven't tested it but if it works let me know!