I am trying to make a circle (actually a flat cylinder) rotate so that the edge crosses two points in world position. These two points can be anywhere on a sphere. The sphere has the same radius and position as the cylinder. the origin of both is [0,0,0].
It's a little bit hard to explain, so I included three pictures that I hope illustrates what I am trying to accomplish.
Here you see what I am trying to accomplish. The yellow circle represents one of the points along the sphere, while the red circle represents the other point. The blue line is actually a flat cylinder going through the sphere, and is rotated so that it goes through both points.
Here is another similar picture, but with the points at different locations.
In this picture one can see the cylinder in full, as the sphere has been hidden.
Now, I am really terrible at math, so I would really love an answer made up of pseudo code or a programming language. And if I should be so lucky, java.
The circles rotation can be represented with either a quaternion or a matrix
So far, what I have tried, is rotating the cylinder with an up vector towards one of the points, and a forward vector towards the other point. But I can't seem to make it work. I have also tried other solutions, most of them involving two rotations (one for each point), but I end up having trouble when combining the rotations.
Here is my current non-working code:
This code makes the circle go through the first point, and then rotates it with an "up vector" towards the same point, this second rotation varies depending on the first point position, and is kind off all over the place.
//calculate direction vector between the two points
point1point2dir.set(point1Pos);
//subtract point two position
point1point2dir.sub(point2Pos);
//normalize
point1point2dir.nor();
//make two quaternions for rotation
Quaternion rot1=new Quaternion();
Quaternion rot2=new Quaternion();
//set first rotation two a rotation between X-axis and point1 position. Vector3.X = (1,0,0)
rot1.set(m.quatUtils.getRot(Vector3.X, point1Pos));
//crossmuliply direction vector between the two points with X-axis
point1point2dir.crs(Vector3.X);
//set the second rotation to a rotation between Z-Axis and the crossmultiplied direction vector
rot2.set(m.quatUtils.getRot(Vector3.Z, point1point2dir));
//multiply the two rotations
rot1.mul(rot2);
//apply the rotation to the cylinders matrix
cylinderMatrix.rotate(rot1);
//the function that gets the quaternion rotation between two vectors
Quaternion getRot(Vector3 pStart, Vector3 pDest) {
start.set(pStart);
dest.set(pDest);
start.nor();
dest.nor();
cosTheta = Vector3.dot(start.x, start.y, start.z, dest.x, dest.y,
dest.z);
rotationAxis.set(0.0f, 0.0f, 0.0f);
if (cosTheta < -1.0f + 0.001f) {
rotationAxis.set(Z_AXIS);
rotationAxis.crs(start);
if (rotationAxis.len2() < 0.01f) {
rotationAxis.set(X_AXIS);
rotationAxis.crs(start);
}
rotationAxis.nor();
resultQuat.set(rotationAxis, 180.0f);
return resultQuat;
}
rotationAxis.set(start);
rotationAxis.crs(dest);
s = (float) Math.sqrt((1 + cosTheta) * 2);
invs = 1.0f / s;
resultQuat.set(rotationAxis.x * invs, rotationAxis.y * invs,
rotationAxis.z * invs, s * 0.5f);
return resultQuat;
}
I would suggest this solution:
Calculate v1 and v2 as the vectors from the center of the sphere to each point that you want the cylinder to pass trough.
Cross product v1 and v2 to get the vector up of the cylinder, let's call it n.
Position the center of the cylinder in the center of the sphere.
Rotate the cylinder using n as vector up.
I figured out the solution! It was actually really simple. I don't know how I managed to bungle the math as much as I did earlier. I actually did spend alot of time on this >:)
Sorry if I wasted anybodys time!
The solution:
find direction vector from point1 (A) to point2 (B).
crossmultiply direction vector with point2 to get (C)
Find the quaternion which represents the rotation from Z-axis to the crossmultiplied direction vector (C), function for doing this included in the code attached to the question.
apply rotation.
Here is the working code (yay):
//the rotation
Quaternion rot=new Quaternion();
//the direction from point1 to point 2 (the point positions are in this case also the direction vectors from center)
point1point2dir.set(point1Pos);
point1point2dir.sub(point2Pos);
point1point2dir.nor();
//crossmultiplied with point2
point1point2dir.crs(point2Pos);
//set the rotation to the rotation between Z-axis and the crossmultiplied direction between point 1 and 2
rot.set(m.quatUtils.getRot(Vector3.Z, point1point2dir));
//apply rotation
ekvatorMatrix.rotate(rot);
And here is the code for the function that returns the quaternion between two vectors:
Quaternion getRot(Vector3 pStart, Vector3 pDest) {
start.set(pStart);
dest.set(pDest);
start.nor();
dest.nor();
cosTheta = Vector3.dot(start.x, start.y, start.z, dest.x, dest.y,
dest.z);
rotationAxis.set(0.0f, 0.0f, 0.0f);
if (cosTheta < -1.0f + 0.001f) {
rotationAxis.set(Z_AXIS);
rotationAxis.crs(start);
if (rotationAxis.len2() < 0.01f) {
rotationAxis.set(X_AXIS);
rotationAxis.crs(start);
}
rotationAxis.nor();
resultQuat.set(rotationAxis, 180.0f);
return resultQuat;
}
rotationAxis.set(start);
rotationAxis.crs(dest);
s = (float) Math.sqrt((1 + cosTheta) * 2);
invs = 1.0f / s;
resultQuat.set(rotationAxis.x * invs, rotationAxis.y * invs,
rotationAxis.z * invs, s * 0.5f);
return resultQuat;
}
Assuming that the initial cylinder is axis aligned with the "circle" ends in positive and negative X direction, and assuming cylinder and sphere is initially unit size (radius=1.0) I would do the following:
Convert the world coordinate representation of the Red and "Yellow" points (let's just for fun call them A and B shall we) to normalized vectors pointing from centre [0,0,0] (from now on called C)
Calculate the angle between CA and CB (which is really just between A and B). Let's call this angle W
Calculate the vector perpendicular to both A and B by doing a cross product. Lets call this new vector D.
Find the rotation matrix that rotates from [0,0,1] to B. Lets call this M1. This can be done in the same way as in point 3 (create a perpendicular vector and rotate identity matrix around it with the angle between the normalized vectors).
Find the rotation matrix that rotates W around D. Let's call this M2
Combine M1 + M2 into M3
You result is M3
This was not tested and so I don't know if it works.
Related
Purpose
I'm implementing a polygon rotation with Java AWT.
I'm already able to draw polygons on the screen, and I'd like to apply a rotation matrix manually upon my polygons coordinates (rotation is done around the lookAt point of the user).
What I've already done
In order to rotate the world, the user first clicks on the screen and then drags the mouse around to perform the rotation.
Let's note the first click point as S, the following point from the drag event as L, and the center of the screen as C.
In order to calculate the rotation angle, when first clicking the screen, I keep a vector from C to S: C-S.
Then, when a drag event occurs, I calculate the vector from C to L: C-L.
I then calculate the angle in radians between C-S to C-L, and that's what I apply on my world.
This works well, and the polygon is indeed rotation around the lookAt point.
My problem
The problem occurs when the user finishes a rotation of PI, and then the polygon is rotating backward.
e.g. When the user starts rotating, the angle starts from 0.1.... 0.2... 1.. 2.. 3.. and in value ~3.1 (I assume PI), the values are starting to go down: 3... 2.. 1.. until 0, and vice versa.
This makes sense since the radians range is [0, PI].
I assume the base vector C-S lies on the right side of X axis, and when the rotation goes down below the X axis the polygon is rotating backwards.
However, I have no idea how to keep the polygon rotating in the same direction all the time (when the user performs a full rotation around the polygon).
Edit
Angle function is:
public final double angle(Vector2D v1)
{
double vDot = this.dot(v1) / ( this.length()*v1.length() );
if( vDot < -1.0) vDot = -1.0;
if( vDot > 1.0) vDot = 1.0;
return ((double) (Math.acos( vDot )));
}
This is a problem of the arcus cosine, acos(cos(x)) is a periodic hat function moving up and down in the range of 0 to pi.
In higher dimensions that can not be avoided, as there is no preferred frame of reference, so there is no way to say that phi should really be -phi. In 2 dimensions there is a prefered orientation of the plane so that one can say what is the first and what the second vector and define a unique angle in positive orientation. Rotate the situation so that the first vector comes to lay on the positive real half axis to get the angle and correct quadrant from the coordinates of the rotated second vector.
Easiest to reconstruct is the complex picture, to compute the angle from a=a.x+i*a.y to b=b.x+i*b.y rotate b back by multiplying with the conjugate of a to get an angle from the zero angle resp. the positive real axis,
arg((a.x-i*a.y)*(b.x+i*b.y))
=arg((a.x*b.x+a.y*b.y)+i*(a.x*b.y-a.y*b.x))
=atan2( a.x*b.y-a.y*b.x , a.x*b.x+a.y*b.y )
Note that screen coordinates use the opposite orientation to the cartesian/complex plane, thus change atan2(y,x) to atan2(-y,x) to get an angle in the usual direction.
public Point rotate(Point original, Point vertex, double angle){
Point translated = new Point(original.x - vertex.x, original.y - vertex.y);
int x = (int)Math.round(translated.x * Math.cos(angle) - translated.y * Math.sin(angle));
int y = (int)Math.round(translated.x * Math.sin(angle) + translated.y * Math.cos(angle));
return new Point(vertex.x+x,vertex.y+y);
}
This is a simple rotation method that you can use to rotate a point around a given vertex.
So i've made my own FPS, graphics and guns and all of that cool stuff; When we fire, the bullet should take a random direction inside the crosshair, as defined by:
float randomX=(float)Math.random()*(0.08f*guns[currentWeapon].currAcc)-(0.04f*guns[currentWeapon].currAcc);
float randomY=(float)Math.random()*(0.08f*guns[currentWeapon].currAcc)-(0.04f*guns[currentWeapon].currAcc);
bulletList.add(new Bullet(new float[]{playerXpos, playerYpos, playerZpos}, new float[]{playerXrot+randomX, playerYrot+randomY}, (float) 0.5));
We calculate the randomness in X and Y (say you had a crosshair size (guns[currentWeapon].currAcc) of 10, then the bullet could go 0.4 to any side and it would remain inside the crosshair.
After that is calculated, we send the player position as the starting position of the bullet, along with the direction it's meant to take (its the player's direction with that extra randomness), and finally it's speed (not important atm, tho).
Now, each frame, the bullets have to move, so for each bullet we call:
position[0] -= (float)Math.sin(direction[1]*piover180) * (float)Math.cos(direction[0]*piover180) * speed;
position[2] -= (float)Math.cos(direction[1]*piover180) * (float)Math.cos(direction[0]*piover180) * speed;
position[1] += (float)Math.sin(direction[0]*piover180) * speed;
So, for X and Z positions, the bullet moves according to the player's rotation on the Y and X axis (say you were looking horizontally into Z, 0 degrees on X and 0 on Y; X would move 0*1*speed and Z would move 1*1*speed).
For Y position, the bullet moves according to the rotation on X axis (varies between -90 and 90), meaning it stays at the same height if the player's looking horizontally or moves completely up if the player is looking vertically.
Now, the problem stands as follows:
If i shoot horizontally, everything works beautifully. Bullets spread around the cross hair, as seen in https://dl.dropbox.com/u/16387578/horiz.jpg
The thing is, if i start looking up, the bullets start concentrating around the center, and make this vertical line the further into the sky i look.
https://dl.dropbox.com/u/16387578/verti.jpg
The 1st image is around 40º in the X axis, the 2nd is a little higher and the last is when i look vertically.
What am i doing wrong here? I designed this solution myself can im pretty sure im messing up somewhere, but i cant figure it out :/
Basicly the vertical offset calculation (float)Math.cos(direction[0]*piover180) was messing up the X and Z movement because they'd both get reduced to 0. The bullets would make a vertical line because they'd rotate on the X axis with the randomness. My solution was to add the randomness after that vertical offset calculation, so they still go left and right and up and down after you fire them.
I also had to add an extra random value otherwise you'd just draw a diagonal line or a cross.
float randomX=(float)Math.random()*(0.08f*guns[currentWeapon].currAcc)-(0.04f*guns[currentWeapon].currAcc);
float randomY=(float)Math.random()*(0.08f*guns[currentWeapon].currAcc)-(0.04f*guns[currentWeapon].currAcc);
float randomZ=(float)Math.random()*(0.08f*guns[currentWeapon].currAcc)-(0.04f*guns[currentWeapon].currAcc);
bulletList.add(new Bullet(new float[]{playerXpos, playerYpos, playerZpos}, new float[]{playerXrot, playerYrot}, new float[]{randomX,randomY, randomZ},(float) 0.5));
And the moving code...
vector[0]= -((float)Math.sin(dir[1]*piover180) * (float)Math.cos(dir[0]*piover180)+(float)Math.sin(random[1]*piover180)) * speed;
vector[1]= ((float)Math.sin(dir[0]*piover180)+(float)Math.sin(random[0]*piover180)) * speed;
vector[2]= -((float)Math.cos(dir[1]*piover180) * (float)Math.cos(dir[0]*piover180)+(float)Math.sin(random[2]*piover180)) * speed;
You didn't need to bust out any complex math, your problem was that when you were rotating the bullet around the y axis for gun spread, if you were looking directly up (that is, through the y axis, the bullet is being rotated around the path which its going, which means no rotation whatsoever (imagine the difference between sticking your arm out forwards towards a wall and spinning in a circle, and sticking you arm out towards the sky and spinning in a circle. Notice that your hand doesn't move at all when pointed towards the sky (the y-axis)) and so you get those "diagonal" bullet spreads.
The trick is to do the bullet spread before rotating by the direction the player is looking in, because that way you know that when you are rotating for spread, that the vector is guaranteed to be perpendicular to the x and y axes.
this.vel = new THREE.Vector3(0,0,-1);
var angle = Math.random() * Math.PI * 2;
var mag = Math.random() * this.gun.accuracy;
this.spread = new THREE.Vector2(Math.cos(angle) * mag,Math.sin(angle) * mag);
this.vel.applyAxisAngle(new THREE.Vector3(0,1,0),this.spread.y / 100); //rotate first when angle gaurenteed to be perpendicular to x and y axes
this.vel.applyAxisAngle(new THREE.Vector3(1,0,0),this.spread.x / 100);
this.vel.applyAxisAngle(new THREE.Vector3(1,0,0),player.looking.x); //then add player looking direction
this.vel.applyAxisAngle(new THREE.Vector3(0,1,0),player.looking.y);
this.offset = this.vel.clone()
I don't use java but I hope you get the main idea of what im doing by this javascript. I am rotating a vector going in the negative z direction (default direction of camera) by the spread.y, and spread.x, and then I am rotating by the pitch and yaw of the angle at which the player is facing.
I will show some images about my problems, so everything will be easier to understand:
Image 1
Image 2
My first image shows the axis (x- axis made of cylinders, y-axis made of cones and z-axis made of spheres) and 3 cylinders positioned as follows:
Cylinder above x-axis (right) supports RotZ(PI/4) and RotX(0). Cylinder above the z-axis(left) supports RotX(PI/4) and RotZ(0). Cylinder in the middle supports RotX(PI/4) and RotZ(PI/4).
My second image shows 3 cylinders at exactly the same angle values, but with a sphere at their origin and changed perspective, to make obvious what is weird: that the upper cylinder(experimentally the "x-axis" cylinder) is closer to the middle cylinder (middle cylinder in the first image) than the lower cylinder ("z-axis) cylinder in the first image). The difference can be seen from any perspective, so not the perspective is the problem.
I have considered that the problem might be the way I am making the rotations. Cylinders have 2f length, so I translate the cylinder to (0,1,0) first, so that the point in the middle of the circle at one end of the cylinder. The idea is that I want to rotate around the (0,0,0) point. Then make the rotations.
Could this be the problem?
The code below shows how cylinders are placed
private void addSimpleBound(float x,float y,float z)
{
Cylinder b=new Cylinder();
TransformGroup tg=new TransformGroup();
tg.addChild(b);
TransformGroup element=translate(tg, new Vector3f(0f,1f,0f));
TransformGroup gr=rotate(element,xAngle,zAngle);
elements.addChild(gr);
}
TransformGroup rotate(Node node,
double xAngle,
double zAngle)
{
Transform3D tiltAxisXform = new Transform3D();
Transform3D tempTiltAxisXform = new Transform3D();
tiltAxisXform.rotX(xAngle);
tempTiltAxisXform.rotZ(zAngle);
tiltAxisXform.mul(tempTiltAxisXform);
TransformGroup rotatedGroup = new TransformGroup(tiltAxisXform);
rotatedGroup.addChild(node);
return rotatedGroup;
}// The rotation method
Edit:
Per comments, the end points of your cylinders are at
(sqrt(0.5), sqrt(0.5), 0),
(0, sqrt(0.5), sqrt(0.5)),
(sqrt(0.5), 0.5, 0.5)
which means that the distances actually are unsymmetric. For a more symmetric result, the second rotation would have to be around the y axis.
Original answer:
This is not weird at all. The ends of your cylinders are at
(sqrt(0.5), sqrt(0.5), 0),
(0, sqrt(0.5), sqrt(0.5)),
(0.5, sqrt(0.5), 0.5)
The distance from the first end to the second is 1, the distance from the first to the third (or from the second to the third) is sqrt(1 - sqrt(0.5)) < 1.
P.S. if you want to make the image more symmetrical, you could put the end of the third cylinder to (sqrt(0.5),0,sqrt(0.5)).
Okay, so I'm trying to get the angle of two quaternions, and it almost works perfectly, but then it jumps from
evec angle: 237.44999653311922
evec angle: 119.60001380112993
and I can't figure out why for the life of me. (Note: evec was a old variable name that just stayed in the print)
Anyway, here's my code:
FloatBuffer fb = BufferUtils.createFloatBuffer(16);
// get the current modelview matrix
GL11.glGetFloat(GL11.GL_MODELVIEW_MATRIX, fb);
Matrix4f mvm = new Matrix4f();
mvm.load(fb);
Quaternion qmv2 = new Quaternion();
Matrix4f imvm = new Matrix4f();
Matrix4f.invert(mvm, imvm);
qmv2.setFromMatrix(imvm);
qmv2.normalise();
Matrix3f nil = new Matrix3f();
nil.setIdentity();
Quaternion qnil = new Quaternion();
qnil.setFromMatrix(nil);
qnil.normalise();
float radAngle = (float)(2.0 * Math.acos(Quaternion.dot(qmv2, qnil)));
System.out.println("evec angle: " + Math.toDegrees(radAngle));
How do I make it stop jumping from 237 to 119 and keep going up to the full 360?
First, what does an angle between two four dimensional vectors (=quaternions) mean to you geometrically? You can calculate it but the result might not make sense. Maybe you are looking for the angle between the axis of the rotations that the two quaternions represent?
Second, you have an error here:
float radAngle = (float)(2.0 * Math.acos(Quaternion.dot(qmv2, qnil)));
^^^^^
The result from acos is the angle. Don't multiply by 2.
Third, the angle between two vectors in a 3D or 4D space can never be greater than 180°. On a plane it can because the plane imposes an orientation. In a 3D space you would have to define an arbitrary direction as "up" to get angles higher than 180°.
I want to rotate an Image in java, initialized with
playerimage = Toolkit.getDefaultToolkit().getImage("C:/Game/player.png"); //Load Player Image
drawed with:
Graphics2D g = buffer.createGraphics();
g.drawImage(playerimage, player.getX(), player.getY(), null); // Draw Player
Now, I want my playerimage, to rotate to my mouse, so it's basically looking to my mouse. How would I do this?
The first thing you'll need to do is get both the mouse pos and the player pos in the same coord system. When you get our mouse pos it will normally be in Screen Coords while your player maybe in your own 'World Coords' space. If the players position is tied directly to pixels then you can skip ahead.
Convert the Mouse Position to World coords..
mouseWorld.X = (mouseScreen.X / screenWidth) * worldWidth;
Once you are in the same coord system you need to find the angle required to rotate. This equation will change based on which way your Player art is facing, lets assume it is facing up the position X axis. Then you can just use the dot product to find the angle between where your player is facing and where the point is pointing.
The dot product is A(dot)B = mag(A) * mag(B) * cos (theta)
mag = magnitude of the vector
theta = angle between the two vectors
So if you normalize the vectors (make them length 1) then..
A(dot)B = 1 * 1 * cos(theta)
A(dot)B = cos(theta)
acos(A(dot)B) = theta
So lets do code...
Vector mouseVec(mouseWorld.X, mouseWorld.Y);
Vector playerVec(playerWorld.X, playerWorld.Y);
//You want to find the angle the player must turn, so pretend the player pos it the origin
mouseVec -= playerVec;
//Create a vector that represents which way your player art is facing
Vector facingVec(1, 0);
mouseVec.Normalize(); //Make their length 1
facingVec.Normalize();
double dotProd = mouseVec.dot(facing);
double angBetween = acos(dotProd);
then call 'rotate' and pass in the angBetween!
Double check that the units are correct, often acos will return 'radians' and the rotate function will take 'degrees' so you need to convert.
More vector info:
Vector Info