I need help with some math related stuff.
I need to rotate a model on the screen with my mouse, but this rotation also needs to be based on the current yaw and pitch of the camera. I've got the first part down, but I don't know how to go on with transforming my roll and pitch to be relative to the camera. Right now, the rotation only works if the camera has not been rotated yet (which is to be expected considering that's what the current code was made to do.)
Also, do note that I do mean roll instead of yaw. I want the roll to be controlled with the left and right swipes of the mouse.
I've read that quaternions can solve the problem, but being a learn-by-example learner, I haven't been able to figure out how to do this.
Here is my current code (Note that PI2 is simply Pi * 2):
float dx = (input.getMouseDX() * prefs.mouseSensitivity);
float dy = (input.getMouseDY() * prefs.mouseSensitivity);
double roll = rollAngle, pitch = pitchAngle;
roll = (roll+dx) % PI2;
if(roll < 0){
roll += PI2;
}
pitch += dy;
rollAngle = (float) yaw;
pitchAngle = (float) pitch;
Also, here's a GIF of the desired effect at all angles (but the GIF was taken at a non-rotated camera, the only point where I mentioned it worked.)
I found the solution after visiting a few math websites and what not.
For anyone in the future who has this problem, here's a basic mathematical formula for it:
Variables:
RPYToRot = Function to convert (roll, pitch, yaw) to rotation matrix
RotToRPY = Opposite of above
RCam = camera rotation (roll, pitch, yaw)
RPos = player rotation (roll, pitch, yaw)
The formula itself being:
RNew = RotToRPY(RPYToRot(RPos)*Rcam)
And here's the code I used:
if (!markedMa){
camMa.setFromEulerAngles((float) Math.toDegrees(-camera.getYaw()), (float) Math.toDegrees(camera.getPitch()), 0f);
markedMa = true;
}
float dx = (input.getMouseDX() * (prefs.mouseSensitivity * 5f));
float dy = (input.getMouseDY() * (prefs.mouseSensitivity * 5f));
utilMatrix.setFromEulerAngles(0f, -dy, -dx);
camMa.mul(utilMatrix);
plaMa.setFromEulerAngles((float) Math.toDegrees(trueYaw), (float) Math.toDegrees(pitch), (float) Math.toDegrees(roll));
plaMa.mul(camMa);
The trick is to get the camera rotation as soon as you start the rotating on the model (assuming you lock the camera orientation), and multiply it by the mouse movement each frame rather than resetting the camera rotation matrix yourself.
plaMa has your final transformation you can then use to apply to your model.
Related
I am trying to use quaternion rotation using the quaternion from JOML: https://github.com/JOML-CI/JOML/blob/main/src/org/joml/Quaternionf.java.
I can get objects to rotate but they get stuck and are unable to complete a full rotation. The object is being updated every frame.
Edit:
Removed all euler related code and am simply trying to get the object to rotate on a single axis based on a certain angle.
Edit 2:
Am trying to use the conjugate and multiplying the quaternions together like I have seen in some videos. I'm not quite there though as the model spins itself off the screen for some reason.
Edit 3:
Normalizing the quaternion fixed the disappearing behaviour. The issue seems to be that there's no simple way to rotate a certain amount without either having a timer to lerp over which will not work in my case as I am trying to rotate an object an arbitrary amount with no set beginning and end.
Rotation function
public void rotate(float angle, float x, float y, float z) {
Quaternionf rot = new Quaternionf();
rot.rotateAxis((float) Math.toRadians(angle), x, y, z);
Quaternionf conjugate = rot.conjugate();
rotation = rot.mul(rotation).mul(conjugate);
}
Calling the rotation function
entity.transform.rotate( 1,0,1, 0);
It does not matter whether you transform your Euler angles into a matrix or a quaternion or whatever representation: As long as you use Euler angles to represent an orientation, Gimbal Lock is unavoidable.
So, in order to avoid Gimbal Lock from happening, you must discard using Euler Angles and stay with one representation for an orientation (like a 3x3 matrix or a quaternion) and apply delta changes to them, instead of trying to represent a full orientation as three Euler angles. So, whenever you - let's say - rotate the object a few degrees around a certain axis, you apply that delta change or orientation to the matrix/quaternion.
I believe I have figured it out.
You need to create a quaternion and rotate it to your delta values, do not manipulate quaternion values directly (e.g. use rotationX function instead).
To add quaternions together you multiply them.
Finally you need to use the equation:
delta quaternion * quaternion to rotate * inverse of delta quaternion
Code:
public void rotate(float x, float y, float z) {
//Use modulus to fix values to below 360 then convert values to radians
float newX = (float) Math.toRadians(x % 360);
float newY = (float) Math.toRadians(y % 360);
float newZ = (float) Math.toRadians(z % 360);
//Create a quaternion with the delta rotation values
Quaternionf rotationDelta = new Quaternionf();
rotationDelta.rotationXYZ(newX, newY, newZ);
//Calculate the inverse of the delta quaternion
Quaternionf conjugate = rotationDelta.conjugate();
//Multiply this transform by the rotation delta quaternion and its inverse
rotation.mul(rotationDelta).mul(conjugate);
}
I make a 2D game in LibGDX and the player movement system is pretty simple:
if (Gdx.input.isKeyPressed(Input.Keys.W))
player.setPosition(player.getPosition().x, player.getPosition().y + 100 * delta);
And the same principle for the A, S and D key.
But now, I have a "drunk mode" - hence, there is a camera rotation in the world, so just:
camera.rotate(MathUtils.random(-50, 50) * delta);
But if I press the W key when the camera rotates, the player doesn't move straight upwards anymore. So, if the rotation is e.g. 180°, the player moves straight downwards, which is actually pretty logical.
So is there a way that the player always moves, upwards when pressing W, rightwards when pressing D etc. no matter what's the current rotation?
Thank you in advance
Try using the deviation on the player in the same direction.
final int PLAYER_SPEED = 100;
float angle = MathUtils.random(-50, 50) * delta; // btw, delta is Gdx.graphics.getDeltaTime()?
camera.rotate(angle);
if (Gdx.input.isKeyPressed(Input.Keys.W)) {
float xOffset = PLAYER_SPEED * sin(toRadians(angle)) * delta;
float yOffset = PLAYER_SPEED * cos(toRadians(angle)) * delta;
player.setPosition(player.getPosition().x + xOffset, player.getPosition().y + yOffset);
}
For other directions there must be other trigonometric formulas.
The solution of 5tingr4y worked for me.
So, I just created a vector and rotated it in the opposite direction. Then, I move the player by that Vector.
I am trying to rotate a box in java using a rotation matrix.
(I am using the LWJGL and Slick 2D libraries)
my code to rotate 1 point around the center point is this:
point1X = (float) (centerX * Math.cos(rotation) - centerY * Math.sin(rotation));
point1Y = (float) (centerX * Math.sin(rotation) + centerY * Math.cos(rotation));
Right now I just update the rotation every update like so:
rotation += delta * 0.001;
This works great except the rotation number does not seem to correspond to a degree from 0˚ to 360˚
Is there a formula or something that will translate the rotation number to a readable degree and vice versa?
Normally, trig functions expect their arguments to be in radians, not degrees.
2*pi radians = 360 degrees.
I'm trying to move my camera in a spherical motion around a model in my world. I've seen converting spherical coordinates(rho, theta, phi) to cartesian coordinates (x, y, z), but I'm not sure how to go about setting this up. Here is what I've tried to so far but it isn't continuously orbiting the model. It gets to a certain point and then the rotation seems to reverse itself.
Initialize theta and phi:
private float theta = 0.0f;
private float phi = 0.0f;
Update theta and phi each frame:
// This should move the camera toward the upper-right continuously, correct?
theta = (theta+1.0f)%360;
phi = (phi+1.0f)%360;
Convert theta and phi to cartesian coordinates for the camera:
camera.position.x = CAMERA_DISTANCE * (float)Math.sin(theta*MathHelper.PIOVER180) * (float)Math.cos(phi*MathHelper.PIOVER180);
camera.position.y = CAMERA_DISTANCE * (float)Math.sin(theta*MathHelper.PIOVER180) * (float)Math.sin(phi*MathHelper.PIOVER180);
camera.position.z = CAMERA_DISTANCE * (float)Math.cos(theta*MathHelper.PIOVER180);
Then update the camera look at point and view matrix:
camera.lookAt(0, 0, 0);
camera.update();
Note:
I am using Java on Android with the libGDX framework and I am trying to control the rotation using an 2D on-screen virtual joystick and I still need to find a way to map the joystick to theta and phi.
Any help is greatly appreciated!
I recently did something just like this. This website helped me a lot to visualize what I needed to do.
What you need to do is convert your local joystick coordinates (relative to it's center) to pitch and yaw values:
public float getPitch()
{
return (position.X - center.X) * MathHelper.PIOVER180;
}
public float getYaw()
{
return (position.Y - center.Y) * MathHelper.PIOVER180;
}
Then you can use a quaternion to represent it's rotation:
public void rotate(float pitch, float yaw, float roll)
{
newQuat.setEulerAngles(-pitch, -yaw, roll);
rotationQuat.mulLeft(newQuat);
}
Then you can apply the quaternion to the camera's view matrix using libGDX's built in rotate(quaternion) method:
camera.view.rotate(rotationQuat);
// Remember to set the camera to "look at" your model
camera.lookAt(0, 0, 0);
I have a 3D arrow drawn with OpenGL that points to the coordinates (0, 0, 0) and I want it to point to a specific GPS location depending on my GPS position and Orientation.
I've tried calculating the azimuth (with my phone's orientation) and adjusting it to be the real north (not the magnetic north).
SensorManager.getOrientation(remappedRotationMatrix, orientation);
// convert radians to degrees
float azimuth = orientation[0];
azimuth = azimuth * 360 / (2 * (float) Math.PI);
GeomagneticField geoField = new GeomagneticField(
Double.valueOf(loc.getLatitude()).floatValue(),
Double.valueOf(loc.getLongitude()).floatValue(),
Double.valueOf(loc.getAltitude()).floatValue(),
System.currentTimeMillis());
// converts magnetic north into true north
azimuth -= geoField.getDeclination();
Then getting the bearing from my Location to the Location I want to point.
target.setLatitude(42.806484);
target.setLongitude(-1.632482);
float bearing = loc.bearingTo(target); // (it's already in degrees)
if (bearing < 0) {
bearing = bearing + 360;
}
float degrees = bearing - azimuth;
if (degrees < 0) {
degrees = degrees + 360;
}
and calculating the degrees I have to rotate the arrow
gl.glRotatef(degrees, 0.0f, 1.0f, 0.0f);
arrow.draw(gl);
Is there someway to do it? Could another possibility be to convert the GPS position to the OpenGL coordinates and use GLU.gluLookAt to point to it?
Thanks.
This seem to be purely a math problem.
Your question is pretty vague, I don't think I can help you without understanding more precisely how your scene is set up and what you want.
Do you know how to use 3D rotation matrices? If not, you probably should learn how they work.
It shouldn't be complicated to calculate the bearing and then rotate the arrow by the degrees you get. I have done the same in 2D although not in OpenGL. I based my code on the Radar sample (http://apps-for-android.googlecode.com/svn/trunk/Radar/). Here is how I draw the 2D arrow:
double bearingToTarget = mBearing - mOrientation;
// Draw an arrow in direction of target
canvas.rotate((float) bearingToTarget, center, center);
final int tipX = center;
final int tipY = center-radius;
canvas.drawLine(center, center, tipX, tipY, mArrowPaint);
final int tipLen = 30;
final int tipWidth = 20;
Path path = new Path();
path.moveTo(tipX, tipY);
path.lineTo(tipX + tipWidth/2, tipY + tipLen);
path.lineTo(tipX - tipWidth/2, tipY + tipLen);
path.lineTo(tipX, tipY);
path.close();
canvas.drawPath(path, mArrowPaint);
canvas.restore();
mBearing is calculated using the method GeoUtils.bearing from the Radar sample which takes care of the complicated math. mOrientation is just the orientation from the sensor listener. So the idea is to compute the difference between the bearing of the GPS location you want to point to (mBearing) and the current orientation of the phone (mOrientation). This gives us the angle bearingToTarget. We then rotate the view about its center by that angle before drawing the arrow along the y axis. This is the same as drawing the arrow rotated by bearingToTarget degrees.
You should be able to apply the same logic in OpenGL by rotating the view about the center of the screen by bearingToTarget degrees before you draw the arrow. Exactly what point you rotate about depends on how your view is set up. To make it simple, make the starting point of your arrow at the origin. Then you can simply rotate about the origin using glRotatef. Otherwise you would first need to translate to the center of the rotation, rotate and then translate back again (this is the common OpenGL technique for rotation about a point).