I am currently working on a Processing (as in the language) sketch, which is driven by Turtle logic (see https://en.wikipedia.org/wiki/Turtle_graphics). This means that I draw a line from the current coordinate to a supplied coordinate. This supplied coordinate will then become the new current coordinate. I want to approximate a circle and have written a simple piece of code using trigonometrics. The code looks as follow:
void drawCircle(int radius){
// The circle center is radius amount to the left of the current xpos
int steps = 16;
double step = TWO_PI /steps;
for(double theta = step; theta <= TWO_PI; theta += step){
float deltaX = cos((float)theta) - cos((float)(theta - step));
float deltaY = sin((float)theta) - sin((float)(theta - step));
moveXY(deltaX*radius, deltaY*radius);
}
}
The program logic is simple. It will use the variable theta to loop through all the radians in a circle. The amount of steps will indicate how large each theta chunk is. It will then calculate the x,y values for the specific point in the circle governed by theta. It will then deduct the x,y values of the previous cycle (hence the theta-step) to get the amount it will have to move from this position to attain the desired x,y position. It will finally supply those delta values to a moveXY function, which draws a line from the current point to the supplied values and makes them the new current position.
The program seems to work quite well when using a limited amount of steps. However, when the step count is increased, the circles become more and more like a Fibonacci spiral. My guess is that this is due to imprecision with the float number and the sine and cosine calculations, and that this adds up with each iteration.
Have I interpreted something wrong? I am looking to port this code to Javascript eventually, so I am looking for a solution in the design. Using BigDecimal might not work, especially since it does not contain its own cosine and sine functions. I have included a few images to detail the problem. Any help is much appreciated!
Step count 16:
Step count 32:
Step count 64:
Step count 128:
Float and sine/cosine should be sufficiently precise. The question is: How precise is your position on the plane? If this position is measured in pixels, then each of your floating point values is rounded to integer after each step. The loss of precision then adds up.
At each iteration round the loop, you are calculating the delta without regard of what the current coordinate is. So effectively, you are "dead-reckoning", which is always going to be inaccurate, since errors at each step build up.
Since you know that you want a circle, an alternative approach would be at each iteration, to first determine the actual point on the circle you want to get to, and then calculate the delta to get there - so something like the following (but I must admit I haven't tested it !):
void drawCircle(int radius){
// The circle center is radius amount to the left of the current xpos
int steps = 16;
double step = TWO_PI /steps;
float previousX = 0;
float previousY = radius;
for(double theta = step; theta <= TWO_PI; theta += step){
float thisX = radius * sin((float)theta);
float thisY = radius * cos((float)theta);
moveXY(thisX - previousX, thisY - previousY);
previousX = thisX;
previousY = thisY;
}
}
Related
My gravity simulation acts more like a gravity slingshot. Once the two bodies pass over each other, they accelerate far more than they decelerate on the other side. It's not balanced. It won't oscillate around an attractor.
How do other gravity simulators get around it? example: http://www.testtubegames.com/gravity.html, if you create 2 bodies they will just oscillate back and forth, not drifting any further apart than their original distance even though they move through each other as in my example.
That's how it should be. But in my case, as soon as they get close they just shoot away from each other to the edges of the imaginary galaxy never to come back for a gazillion years.
edit: Here is a video of the bug https://imgur.com/PhhRhP7
Here is a minimal test case to run in processing.
//Constants:
float v;
int unit = 1; //1 pixel = 1 meter
float x;
float y;
float alx;
float aly;
float g = 6.67408 * pow(10, -11) * sq(unit); //g constant
float m1 = (1 * pow(10, 15)); // attractor mass
float m2 = 1; //object mass
void setup() {
size (200,200);
a = 0;
v = 0;
x = width/2; // object x
y = 0; // object y
alx = width/2; //attractor x
aly = height/2; //attractor y
}
void draw() {
background(0);
getAcc();
applyAcc();
fill(0,255,0);
ellipse(x, y, 10, 10); //object
fill(255,0,0);
ellipse(alx, aly, 10, 10); //attractor
}
void applyAcc() {
a = getAcc();
v += a * (1/frameRate); //add acceleration to velocity
y += v * (1/frameRate); //add velocity to Y
a = 0;
}
float getAcc() {
float a = 0;
float d = dist(x, y, alx, aly); //distance to attractor
float gravity = (g * m1 * m2)/sq(d); //gforce
a += gravity/m2;
if (y > aly){
a *= -1;}
return a;
}
Your distance doesn't include width of the object, so the objects effectively occupy the same space at the same time.
The way to "cap gravity" as suggested above is add a normal force when the outer edges touch, if it's a physical simulation.
You should get into the habit of debugging your code. Which line of code is behaving differently from what you expected?
For example, if I were you I would start by printing out the value of gravity every time you calculate it:
float gravity = (g * m1 * m2)/sq(d); //gforce
println(gravity);
You'll notice that your gravity value skyrockets as your circles get closer to each other. And this makes sense, because you're dividing by sq(d). Ad d gets smaller, your gravity increases.
You could simply cap your gravity value so it doesn't go off the charts anymore:
float gravity = (g * m1 * m2)/sq(d);
if(gravity > 100){
gravity = 100;
}
Alternatively you could cap d so it never goes below a certain value, but the result is the same.
In the end you'll find that this is not going to be as easy as you expected. You're going to have to tune the parameters quite a bit so your simulation works how you want.
Working demo here: https://beta.observablehq.com/#shaunlebron/1d-gravity
I followed the solution posted by the author of the sim that inspired this question here:
-First off, shrinking the timestep is always helpful. My simulation runs, as a baseline, about 40 ‘steps’ per frame, and 30 frames per second.
-To deal with the exact issue you talk about, I think modeling the bodies not as pure point masses - but rather spherical masses with a certain radius will be vital. That prevents the force of gravity from diverging to infinity. So, for instance, if you drop an asteroid into a star in my simulation (with collisions turned off), the force of gravity will increase as the asteroid gets closer, up until it reaches the surface of the star, at which point the force will begin to decrease. And the moment it’s at the center of the star (or nearby), the force will be zero (or nearly zero) - instead of near-infinite.
In my demo, I just completed turned off gravity when two objects are close enough together. Seems to work well enough.
I am currently creating a 2D space game in Java, in which you control a ship in, well, space.
The game does not use any external libraries.
The ship is supposed to move towards the cursor. However, when moving the cursor, the old force does not magically disappear; the ship changes its course, over time, to eventually move in the desired direction.
However, I have run into an issue regarding the movement of the ship.
Basically, what I want to achieve is crudely illustrated by this image:
The image shows how the ship is supposed to move during one game tick.
To explain further:
The Ship's max speed is illustrated by the circle.
The Target Angle is where the cursor currently is.
The Current Angle is the direction that the ship is currently traveling.
The Current Angle should move closer and closer to the Target Angle until it reaches the point where these two angles are the same.
The ship should change direction toward the target angle taking the shortest route possible; it can turn both left and right, not just left or right.
Now I've explained what I want to achieve, now I will instead describe what I so far have achieved and how it works.
Basically, the "ship" is an image sitting in the center of the screen. When you "move" the ship, the ship stays put; what moves is the rest of the play area.
The current "position" of the ship relative to the coordinate system that represents the play area are the integers xPos and yPos.
Now for some sample code that shows how the system works:
int xPos;
int yPos;
public void updateMovement() {
xPos += xSpeed;
yPos += ySpeed;
}
public void moveForward() {
double yTempSpeed = ySpeed;
double xTempSpeed = xSpeed;
yTempSpeed += 0.01 * Math.sin(Math.toRadians(targetAngle));
xTempSpeed += 0.01 * Math.cos(Math.toRadians(targetAngle));
double resultVector = Math.sqrt(xTempSpeed * xTempSpeed + yTempSpeed * yTempSpeed);
if (resultVector < 2) {
ySpeed += 0.01 * Math.sin(Math.toRadians(targetAngle));
xSpeed += 0.01 * Math.cos(Math.toRadians(targetAngle));
}
This code successfully sets the Ship's max speed to the desired value, however, this does not work (the ships' course does not change) in the event where the resulting "vector" is larger than 2, i.e. when the speed is already at it's maximum and the targetAngle is too close to the angle which the ship is currently traveling (+- Pi / 2).
How would I go about changing the current angle based on this implementation?
public void moveForward() {
ySpeed += 0.01 * Math.sin(Math.toRadians(targetAngle));
xSpeed += 0.01 * Math.cos(Math.toRadians(targetAngle));
double currentSpeed = Math.sqrt(xTempSpeed * xTempSpeed + yTempSpeed * yTempSpeed);
if (currentSpeed > maxSpeed) {
//the resulting speed is allways <= maxspeed (normed to that)
ySpeed *= maxSpeed/currentSpeed;
xSpeed *= maxSpeed/currentSpeed;
}
hope this is what you needed... although it is quite unrealistic, that a spacecraft has a maximum Speed, but in terms of "playability" i would do the same.
What about normalizing speed of the ship, not to let it actually exceed your speed limit (=2):
//it's good to put all constants out of a function in one place
//to make it easier if you ever wanted to change it
private final int MAX_SPEED = 2;
private final double ACCEL_FACTOR = 0.01;
public void moveForward() {
ySpeed += ACCEL_FACTOR * Math.sin(Math.toRadians(targetAngle));
xSpeed += ACCEL_FACTOR * Math.cos(Math.toRadians(targetAngle));
//normalize ship speed, i.e. preserve ratio of xSpeed/ySpeed
//but make sure that xSpeed^2 + ySpeed^2 <= MAX_SPEED^2
//your code goes here
//...
}
Read about vector normalization. This way, the changes of ship speed will be applied normally (at this moment speed can be >= MAX_SPEED), but after normalization it will never get greater than MAX_SPEED, so your if instruction is not even needed.
You may find the following (Swift) code useful, although you would need to handle the per-frame integration of the ship's linear and angular velocities yourself:
func moveShipTowards(location: CGPoint) {
if let ship = shipNode? {
let distanceVector = CGVector(origin: ship.position, point: location)
let targetAngle = distanceVector.angle
let shipAngle = ship.zRotation
var dø = targetAngle - shipAngle
// convert to shortest arc
if dø > π {
dø -= 2.0 * π
} else if dø < -π {
dø += 2.0 * π
}
// resulting angular velocity
ship.physicsBody.angularVelocity = 12 * dø.clampedTo(π)
var velocityUnitVector = CGVector(cosf(ship.zRotation), sinf(ship.zRotation))
var magnitude = distanceVector.length.clampedTo(400)
ship.physicsBody.velocity = velocityUnitVector * magnitude
}
}
It handles having the ship deccelerate as the target point is approached. Looking at it just now, it doesn't appear to handle acceleration properly though.
This question already has answers here:
What algorithm can I use to determine points within a semi-circle?
(11 answers)
Closed 8 years ago.
I have a points x and y and I need to check the point which intersect within a semi- pie.
I need an algorithm to find the point is intersect in the semi circle. for rectangle we have point contains method to check the point intersect the rectangle, but this doesn't work for semi circle segments with start and end angle.
I have created many semi pie segment, when I touch the pie segment, I need to check the point intersect of which segment.
I'm assuming this is speed-critical, and also that you want to be able to specify the semi-circle as having a center and arbitrary start and end angles, so not just a semi-circle but a circular sector. For a semi-circle just make the start and end angles 180 degrees apart, or remove the test against endVector.
Make the test a two-step process. For a given point, first check that it is inside a rectangle enclosing the semi-circle. You can use your existing sort and binary search algorithm for this. If a point is not in the rectangle then reject the point, if it is then test against the semi-circle.
Also, outside of the loop, convert the values specifying the semi-circle into a form that will enable the tests to be done faster:
Convert the radius to radius squared. Compare the distance of a point from the center squared, to the radius squared. This saves a square root when computing the distance.
Convert the start and end angles of the semi-circle to a couple of 2D vectors, and then use these to check whether the point is inside the sector. Then you won't have to use any trig functions like atan2(y, x), which are slow, or do annoying fiddly comparisons to start and end angles and handling the case where angles wrap around from 360 to 0.
pseudo code:
float radiusSquared;
float startVectorX;
float startVectorY;
float endVectorX;
float endVectorY;
float centerX;
float centerY;
void convertSector(float radius, float startAngle, float endAngle)
{
radiusSquared = radius * radius;
startVectorX = cos(startAngle);
startVectorY = sin(startAngle);
endVectorX = cos(endAngle);
endVectorY = sin(endAngle);
}
boolean testPoint(float x, float y)
{
// check if point is within the radius:
float distanceX = x - centerX;
float distanceY = y - centerY;
if((distanceX * distanceX) + (distanceY * distanceY)) > radiusSq)
return false;
// check if point is outside start radius vector using 2D dot-product with normal:
if(((distanceX * -startVectorY) + (distanceY * startVectorX)) < 0.0f)
return false;
// check if point is outside end radius vector using 2D dot-product with normal:
if(((distanceX * -endVectorY) + (distanceY * endVectorX)) > 0.0f)
return false;
return true;
}
The above code will only work for sectors with an internal angle of <= 180 degrees. To test a point against sectors larger than that (i.e. Pac-Man like shapes), test if the point is inside the circle and then test that it is not inside the sector making up the remainder of the circle.
I have a character in my game that must rotate smoothly to get to a desired angle. Consider angle as the current angle and touchAngle as the desired angle which is always between 0 to 360. I want to add +1/-1 to current angle in every game update to get to the desired touchAngle. The problem is first it must chose direction and it must be between 0 to 360. this is my pseudo code:
int touchAngle;
float angle;
public void update()
{
if ((int)angle != touchAngle) angle += ???
}
Since you have values that are always normalized in the interval [0 360] this should not be too hard.
You just need to distinguish two different cases:
angle < touchAngle
angle > touchAngle
in the first case we want to rotate counterclockwise so the update has to be angle =+ 1 (assuming that you want to turn of 1 every update cycle).
In the second case we want to turn clockwise so the update should be angle -= 1.
The problem is that this is not always the shortest way to rotate. For instance if:
angle == 359
touchAngle == 1
we don't want to make all the way 358, 357, 356...instead we want to rotate counterclockwise for just 2 units: 360, 1.
This can be achieved comparing the distance between the angles abs(angle - touchAngle).
If this value is bigger than 180 it means we are going the wrong way, so we have to do the way around so
if(angle < touchAngle) {
if(abs(angle - touchAngle)<180)
angle += 1;
else angle -= 1;
}
else {
if(abs(angle - touchAngle)<180)
angle -= 1;
else angle += 1;
}
of course all of this until ((int)angale != touchAngle).
I might have made mistakes with the cases but this is the principle.
Generally you want to bring in time to the equation, so that you can smoothly change the angle over time. Most setups have a way to get a time it took to render the previous frame and the typical way to do this is to say..
int touchAngle;
float angle;
float deltaTime; //Time it took to render last frame, typically in miliseconds
float amountToTurnPerSecond;
public void update()
{
if((int)angle != touchAngle) angle += (deltaTime * amountToTurnPerSecond);
}
This will make it so that each second, your angle is changed by amountToTurnPerSecond, but changed slowly over each frame the correct amount of change so that it is smooth. Something to note about this is that you wont evenly end up at touchAngle most of the time, so checking to see if you go over and instead setting to touchAngle would be a good idea.
Edit to follow up on comment:
I think the easiest way to attain the correct direction for turn is actually not to use angles at all. You need to get the relative direction from your touch to your character in a 2d space. Typically you take the touch from screen space to world space, then do the calculations there (at least this is what I've done in the past). Start out by getting your touch into world space, then use the vector cross product to determine direction. This looks kind of like the following...
character position = cx, cy
target position = tx, ty
current facing direction of character = rx, ry
First we take the distance between the character and the target position:
dx = tx - cx
dy = ty - cy
This not only gives us how far it is from us, but essentially tells us that if we were at 0, 0, which quadrant in 2d space would the target be?
Next we do a cross product:
cross_product = dx * ry - dy * rx
If this is positive you go one way, if it's negative you go the other. The reason this works out is that if the distance is for instance (-5, 2) then we know that if we are facing directly north, the point is to our left 5 and forward 2. So we turn left.
I have a 2D convex polygon in 3D space and a function to measure the area of the polygon.
public double area() {
if (vertices.size() >= 3) {
double area = 0;
Vector3 origin = vertices.get(0);
Vector3 prev = vertices.get(1).clone();
prev.sub(origin);
for (int i = 2; i < vertices.size(); i++) {
Vector3 current = vertices.get(i).clone();
current.sub(origin);
Vector3 cross = prev.cross(current);
area += cross.magnitude();
prev = current;
}
area /= 2;
return area;
} else {
return 0;
}
}
To test that this method works at all orientations of the polygon I had my program rotate it a little bit each iteration and calculate the area. Like so...
Face f = poly.getFaces().get(0);
for (int i = 0; i < f.size(); i++) {
Vector3 v = f.getVertex(i);
v.rotate(0.1f, 0.2f, 0.3f);
}
if (blah % 1000 == 0)
System.out.println(blah + ":\t" + f.area());
My method seems correct when testing with a 20x20 square. However the rotate method (a method in the Vector3 class) seems to introduce some error into the position of each vertex in the polygon, which affects the area calculation. Here is the Vector3.rotate() method
public void rotate(double xAngle, double yAngle, double zAngle) {
double oldY = y;
double oldZ = z;
y = oldY * Math.cos(xAngle) - oldZ * Math.sin(xAngle);
z = oldY * Math.sin(xAngle) + oldZ * Math.cos(xAngle);
oldZ = z;
double oldX = x;
z = oldZ * Math.cos(yAngle) - oldX * Math.sin(yAngle);
x = oldZ * Math.sin(yAngle) + oldX * Math.cos(yAngle);
oldX = x;
oldY = y;
x = oldX * Math.cos(zAngle) - oldY * Math.sin(zAngle);
y = oldX * Math.sin(zAngle) + oldY * Math.cos(zAngle);
}
Here is the output for my program in the format "iteration: area":
0: 400.0
1000: 399.9999999999981
2000: 399.99999999999744
3000: 399.9999999999959
4000: 399.9999999999924
5000: 399.9999999999912
6000: 399.99999999999187
7000: 399.9999999999892
8000: 399.9999999999868
9000: 399.99999999998664
10000: 399.99999999998386
11000: 399.99999999998283
12000: 399.99999999998215
13000: 399.9999999999805
14000: 399.99999999998016
15000: 399.99999999997897
16000: 399.9999999999782
17000: 399.99999999997715
18000: 399.99999999997726
19000: 399.9999999999769
20000: 399.99999999997584
Since this is intended to eventually be for a physics engine I would like to know how I can minimise the cumulative error since the Vector3.rotate() method will be used on a very regular basis.
Thanks!
A couple of odd notes:
The error is proportional to the amount rotated. ie. bigger rotation per iteration -> bigger error per iteration.
There is more error when passing doubles to the rotate function than when passing it floats.
You'll always have some cumulative error with repeated floating point trig operations — that's just how they work. To deal with it, you basically have two options:
Just ignore it. Note that, in your example, after 20,000 iterations(!) the area is still accurate down to 13 decimal places. That's not bad, considering that doubles can only store about 16 decimal places to begin with.
Indeed, plotting your graph, the area of your square seems to be going down more or less linearly:
This makes sense, assuming that the effective determinant of your approximate rotation matrix is about 1 − 3.417825 × 10-18, which is well within normal double precision floating point error range of one. If that's the case, the area of your square would continue a very slow exponential decay towards zero, such that you'd need about two billion (2 × 109) 7.3 × 1014 iterations to get the area down to 399. Assuming 100 iterations per second, that's about seven and a half months 230 thousand years.
Edit: When I first calculated how long it would take for the area to reach 399, it seems I made a mistake and somehow managed to overestimate the decay rate by a factor of about 400,000(!). I've corrected the mistake above.
If you still feel you don't want any cumulative error, the answer is simple: don't iterate floating point rotations. Instead, have your object store its current orientation in a member variable, and use that information to always rotate the object from its original orientation to its current one.
This is simple in 2D, since you just have to store an angle. In 3D, I'd suggest storing either a quaternion or a matrix, and occasionally rescaling it so that its norm / determinant stays approximately one (and, if you're using a matrix to represent the orientation of a rigid body, that it remains approximately orthogonal).
Of course, this approach won't eliminate cumulative error in the orientation of the object, but the rescaling does ensure that the volume, area and/or shape of the object won't be affected.
You say there is cumulative error but I don't believe there is (note how your output desn't always go down) and the rest of the error is just due to rounding and loss of precision in a float.
I did work on a 2d physics engine in university (in java) and found double to be more precise (of course it is see oracles datatype sizes
In short you will never get rid of this behaviour you just have to accept the limitations of precision
EDIT:
Now I look at your .area function there is possibly some cumulative due to
+= cross.magnitude
but I have to say that whole function looks a bit odd. Why does it need to know the previous vertices to calculate the current area?