Bit of back story on my project (because your probably going to say, "Just use OpenGL.")
a.k.a. The Reason I Hate OpenGL
So, my computer is around 7 years old, with OpenGL 3.30 on my hardware (Using an ATI Radeon HD 4300/4500), and I really wanted to make my own 3D game engine using OpenGL. Long story very short, shaders are broken for me (detail at bottom). So, I said screw it I'm going to make my own 3D renderer. I got the rendering done, but I need help on rotations.
The More Boring Story of why this happened
Well, the truth is I started making my own engine because my laptop was too old to follow the tutorial, so I started work on a 2D engine with a cool parallax system, then it hit me. The reason why I said the story about my computer being to old is because that is really when I started thinking about rotation on a whole different level.
The Code
This is where the problems started:
x = (float) ((x * Math.cos(Math.toRadians(main.cameraRot)) - z * Math.sin(Math.toRadians(main.cameraRot))));
z = (float) (z * Math.sin(Math.toRadians(main.cameraRot)) - x * Math.cos(Math.toRadians(main.cameraRot)));
I think I see the problem, I think I need to add zsin(cameraRot) to xcos(cameraRot)
I've even drawn out how Sine and Cosine functions work, mainly because I was also working on a water effect at the time, and I thought, "I should be able to use sin (and only sin) to rotate." I was wrong (obviously). I have also used a graphing calculator (TI-83+ from 1999, not 2011) to graph those functions (they were a bit different, and yes I used the graphing mode where you could input both X and Y values). I'm trying out some stuff right now, and I think I see the main problem. The Z rotation uses the X rotation, but the X rotation uses the Z coordinate (If that was complicated, I'll break it down: We need to use the original X value to calculate the rotation for the Z value, but we instead use the X value after it's changed to a rotated X, which would make a huge difference).
It doesn't work at all. cameraRot (rotation) is constantly increasing, but there is no effect.
Here's the code for rendering:
g.drawRect((int) (x / (z - main.camZ) - 256 / (z - main.camZ) / 2),(int) (0 - 256 / (z - main.camZ) / 2),(int) (256 / (z - main.camZ)),(int) (256 / (z - main.camZ)));
But it screws up rendering. Yes, I know that I'm drawing a square, but the previous one I made (on a laptop) was more complete, but used the same rendering algorithm and broke when I tried adding in rotations, every object appeared to be at 0,0,1.
Any suggestions on how to fix this?
Is it from the rendering or the equation? (I'm going to save you the trouble and tell you its the rotation equation.
Just a little side note, I accidentally made that rendering algorithm.
How the Shaders were Broken
I tried using uniforms with VertexShaders but they only work with Fragment Shaders. The tutorial I followed used "#version 330" (like me) but they had OpenGL version 4.3 (and they were using java) and I had version 3.30 (and used java).
okay so I was just dumb.
x = (float) ((x * Math.cos(Math.toRadians(main.cameraRot)) - z * Math.sin(Math.toRadians(main.cameraRot))));
z = (float) (z * Math.sin(Math.toRadians(main.cameraRot)) - x * Math.cos(Math.toRadians(main.cameraRot)));
The reason why I was dumb is because what you would do is (simplified, using doubles):
double tempX = x;
x = tempX * Math.cos(theta) - z * Math.sin(theta);
z = tempX * Math.sin(theta) + z * Math.cos(theta);
Two years later, and I've written this equation so many times now that it is literally going to haunt me forever. Even after I die.
Now to look at something I said:
I think I see the problem, I think I need to add zsin(cameraRot) to xcos(cameraRot)
Not even close. The whole issue I was had the order of z and x wrong for the z equation, as well as I didn't store the x in a temporary variable before doing the operations. If you don't do that, it doesn't rotate correctly on the z axis.
I might as well add in some notes to this obscure question that's actually quite easy to figure out. If you have 200 objects, each calling Math.sin() and Math.cos() twice each, that's highly inefficient. The very least you could do is store the resultant Math.sin() and Math.cos() values in their own variable, but that's just scratching the surface.
Instead of making it so each object calculates the sine and cosine individually, you make a Camera class that holds all the positional and rotational data for the camera, as well as the screen dimensions. This is so that a rectangle sized 1.0 x 1.0 is proportional to a screen. In non-square aspect ratios (e.g. 16:9, 4:3), you can see some stretching going on. The easy way to fix this is to pass the screen's width or height for both screenWidth and screenHeight. The harder way let's you use any FoV, but that's another day.
public class Camera {
// Position variables, X is horizontal, Y is vertical, Z is fordical (forward and backward)
private double x, y, z;
private double rot;
private double cos;
private double sin;
// Variables pertaining to the screen
private int screenWidth;
private int screenHeight;
public Camera(double x, double y, double z, double rot, int screenWidth, int screenHeight) {
setScreenDimensions(screenWidth, screenHeight);
setPosition(x, y, z);
setRotation(rot);
}
public void setScreenDimensions(int screenWidth, int screenHeight) {
this.screenWidth = screenWidth;
this.screenHeight = screenHeight;
}
public int getScreenWidth() {...}
public int getScreenHeight() {...}
public void setPosition(double x, double y, double z) {...}
public void setRotation(double rot) {
// Update the rotation variable
this.rot = rot;
// Update the trig values
cos = Math.cos(rot);
sin = Math.sin(rot);
}
// Position Getters
public double getX() {...}
public double getY() {...}
public double getZ() {...}
// Position Setters
public void setX(double x) {...}
public void setY(double y) {...}
public void setZ(double z) {...}
public double getRotation() { return rot; }
public double getCos() { return cos; }
public double getSin() { return sin; }
}
and then when you transform from world-space (3D) to screen-space (2D), you would do (using Java Swing because that's what I was using at the time of asking this question):
// somewhere in a class, probably the base GameObject class. If it's in an util class, make it "public static", and if it's in the Camera class, it would be "public".
protected Point transform(Camera camera, double x, double y, double z) {
double transX = x - camera.getX();
double transY = y - camera.getY();
double transZ = z - camera.getZ();
double rotX = transX * camera.getCos() - transZ * camera.getSin();
double rotZ = transX * camera.getSin() + transZ * camera.getCos();
int pointX = (transX * camera.getScreenWidth() ) / transZ;
int pointY = (y * camera.getScreenHeight()) / transZ;
return new Point(pointX, pointY);
}
Bam now we've optimized the rendering routine. At least, as far as a sane person would go. If you're really religious about optimization (like I sometimes am), you can probably do some crazy stuff. The real crazy stuff happens when you get to C++ and use pointers, but that's beyond the scope of this Java question. Hell, even going into the optimization part is sort of beyond the scope of the question, but it's my question and I think I reserve the right to answer it as I see fit, as long as it benefits anybody reading my answer.
note: I wrote this late at night so I apologize for any errors, will fix in the morning!
side-note: it's not like anybody's gonna read this anyways :)
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 making a 3D Java game but i have got problems when rotating a hitbox. I, upon this point, only used a method which detects if a Vector3f is in a box.
But in my game I want to rotate houses, for example, so that method won't work. I could use circulair hitboxes but that wouldn't work for every instance of objects.
So far i have used this simple calculation to detect if a location is in a hitbox.
public boolean isinbox(Vector3f pos) {
Vector3f entPos = ent.getPosition();
float x1 = entPos.x + xOffset;
float z1 = entPos.z + zOffset;
float y1 = entPos.y + yOffset;
float x2 = entPos.x - xOffset;
float z2 = entPos.z - zOffset;
float y2 = entPos.y;
return pos.x < x1 && pos.x > x2 && pos.z < z1 && pos.z > z2 && pos.y > y2 && pos.y < y1;
}
This works in many ways, but I can't figure out how to rotate them and still be able to detect it. The xOffset is the ofset if side a to the center and negative side b to the center.
How would I be able to rotate a hitbox and detect if an Vector is in it?
There are a couple of ways of getting around this issue and one (or more) ways of solving this issue:
Solving It
SAT Collision
SAT Stands for Separating Axis Theorem.
TutsPlus and MetaSoftware are great websites to learn how it works and how to implement it.
The Separating Axis Theorem (SAT for short) essentially states if you are able to draw a line to separate two polygons, then they do not collide. It's that simple. (gamedevelopment.tutsplus.com)
Here is the basic idea:
It can also be used for more complex shapes:
Getting Around It
AABB Collision
This is done by assuming that collisions will only ever happen on the X/Y axis (never on an axis defined by an arbitrary line).
Here is an example of what this looks like:
Therefore, you must define this axis-aligned hitbox by using the minimum X-and-Y values and the maximum X-and-Y values of the original box.
Circle Collision
This is a much simpler collision check which only detects when two objects are within a certain distance of each other.
Here is an example of what this looks like:
The way this works is that if the distance between the two objects is less than the sum of each circle's radius, then the objects are colliding.
Using an External Library
Bullet Physics
Box2D
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;
}
}
I have tried to make an algorithm in java to rotate a 2-d pixel array(Not restricted to 90 degrees), the only problem i have with this is: the end result leaves me with dots/holes within the image.
Here is the code :
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
int xp = (int) (nx + Math.cos(rotation) * (x - width / 2) + Math
.cos(rotation + Math.PI / 2) * (y - height / 2));
int yp = (int) (ny + Math.sin(rotation) * (x - width / 2) + Math
.sin(rotation + Math.PI / 2) * (y - height / 2));
int pixel = pixels[x + y * width];
Main.pixels[xp + yp * Main.WIDTH] = pixel;
}
}
'Main.pixels' is an array connected to a canvas display, this is what is displayed onto the monitor.
'pixels' and the function itself, is within a sprite class. The sprite class grabs the pixels from a '.png' image at initialization of the program.
I've tried looking at the 'Rotation Matrix' solutions. But they are too complicated for me. I have noticed that when the image gets closer to a point of 45 degrees, the image is some-what stretched ? What is going wrong? And what is the correct code; that adds the pixels to a larger scale array(E.g. Main.pixels[]).
Needs to be java! and relative to the code format above. I am not looking for complex examples, simply because i will not understand(As said above). Simple and straight to the point, is what i am looking for.
How id like the question to be answered.
Your formula is wrong because ....
Do this and the effect will be...
Simplify this...
Id recommend...
Im sorry if im asking to much, but i have looked for an answer relative to this question, that i can understand and use. But to always either be given a rotation of 90 degrees, or an example from another programming language.
You are pushing the pixels forward, and not every pixel is hit by the discretized rotation map. You can get rid of the gaps by calculating the source of each pixel instead.
Instead of
for each pixel p in the source
pixel q = rotate(p, theta)
q.setColor(p.getColor())
try
for each pixel q in the image
pixel p = rotate(q, -theta)
q.setColor(p.getColor())
This will still have visual artifacts. You can improve on this by interpolating instead of rounding the coordinates of the source pixel p to integer values.
Edit: Your rotation formulas looked odd, but they appear ok after using trig identities like cos(r+pi/2) = -sin(r) and sin(r+pi/2)=cos(r). They should not be the cause of any stretching.
To avoid holes you can:
compute the source coordinate from destination
(just reverse the computation to your current state) it is the same as Douglas Zare answer
use bilinear or better filtering
use less then single pixel step
usually 0.75 pixel is enough for covering the holes but you need to use floats instead of ints which sometimes is not possible (due to performance and or missing implementation or other reasons)
Distortion
if your image get distorted then you do not have aspect ratio correctly applied so x-pixel size is different then y-pixel size. You need to add scale to one axis so it matches the device/transforms applied. Here few hints:
Is the source image and destination image separate (not in place)? so Main.pixels and pixels are not the same thing... otherwise you are overwriting some pixels before their usage which could be another cause of distortion.
Just have realized you have cos,cos and sin,sin in rotation formula which is non standard and may be you got the angle delta wrongly signed somewhere so
Just to be sure here an example of the bullet #1. (reverse) with standard rotation formula (C++):
float c=Math.cos(-rotation);
float s=Math.sin(-rotation);
int x0=Main.width/2;
int y0=Main.height/2;
int x1= width/2;
int y1= height/2;
for (int a=0,y=0; y < Main.height; y++)
for (int x=0; x < Main.width; x++,a++)
{
// coordinate inside dst image rotation center biased
int xp=x-x0;
int yp=y-y0;
// rotate inverse
int xx=int(float(float(xp)*c-float(yp)*s));
int yy=int(float(float(xp)*s+float(yp)*c));
// coordinate inside src image
xp=xx+x1;
yp=yy+y1;
if ((xp>=0)&&(xp<width)&&(yp>=0)&&(yp<height))
Main.pixels[a]=pixels[xp + yp*width]; // copy pixel
else Main.pixels[a]=0; // out of src range pixel is black
}
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?