I am getting 2d coordinates from Vector3. I have to correct positions to get right results. Indeed it seems I get correct positions like this but I do not know how to correct position when rotated.
Here my worldViewMatrix that I do operations but those operations not passed to my VertexData then I try to correct positions.
WorldViewMatrix = Matrix.Scaling(Scale) * Matrix.RotationX(Rotation.X) * Matrix.RotationY(Rotation.Y) * Matrix.RotationZ(Rotation.Z) * Matrix.Translation(Position.X, Position.Y, Position.Z) * viewProj;
I am trying to correct it like:
public Vector2 Convert_3Dto2D(Vector3 position, Vector3 translation, Vector3 scale, Vector3 rotation, Matrix viewProj, RenderForm_EX form)
{position += translation;
position += translation;
position = Vector3.Multiply(position, scale);
//ROTATION ?
var project = Vector3.Project(position, 0, 0, form.ClientSize.Width, form.ClientSize.Height, 0, 1, viewProj);
Console.WriteLine(project.X+" "+ project.Y);
return new Vector2(project.X, project.Y);
}
What can I do to correct rotated position ?
If you can, post a little more information about "correct positions". I will take a stab at this and assume you want to move your vertex into world space, then work out what pixel it occupies.
Usually you order multiplying your order by
Translate * Rotate * Scale;
if you want Viewprojection to apply correctly, I believe it should be at the start. V * (t * r * s).
The following link on gamedev stackexchange goes into this. matrix order
Also, your project takes in a Vector3 that has been already multiplied into wvp matrix, I dont see you have multiplied it in your convert_3dto2d function.
Basically, execute a TRS matrix multiply on your original vert, then multiply your WVP matrix then execute your project. You will then get your screen space pixel.
Related
So... I'll try to be as clear as possible, if I let something unclear please let me know.
I have a vector that comes from origin and go to a point in space, and I have an object that I want it's transform.up (Or Y vector) to be colinear with this vector, but the Y rotation of this object is driven by another factor, and I dont want to change it.
So far, what I'm trying to do is project this vector in the local XY and local ZY planes and measure the angles and apply rotation:
float xInclination = Mathf.Atan(Vector3.ProjectOnPlane(orbitMomemtumVector, transform.right).z / Vector3.ProjectOnPlane(orbitMomemtumVector, transform.right).y)*Mathf.Rad2Deg;
float yInclination = Mathf.Atan(initialPos.z / initialPos.x) * Mathf.Rad2Deg;
float zInclination = -Mathf.Atan(Vector3.ProjectOnPlane(orbitMomemtumVector, transform.forward).x / Vector3.ProjectOnPlane(orbitMomemtumVector, transform.forward).y)*Mathf.Rad2Deg;
if (initialPos.x < 0 && initialPos.z > 0)
{
yInclination = 180f - Mathf.Abs(yInclination);
argumentPeriapsis = argumentPeriapsis - yInclination;
}
else if (initialPos.x < 0 && initialPos.z < 0)
{
yInclination = 180f + Mathf.Abs(yInclination);
argumentPeriapsis = argumentPeriapsis - yInclination;
}
else
{
argumentPeriapsis = argumentPeriapsis - yInclination;
}
transform.rotation = Quaternion.Euler(xInclination, (float)argumentPeriapsis, zInclination);
This image shows the problem, I need the Y arrow to be collinear with the blue line
Let me be clear on this, don't use Euler angles in 3d space. In fact, avoid them in 2d games as well. Unless your object truly rotates on a single axis, and you never have to get the angle between two rotations, or lerp your rotations, don't use them.
What you want is Quaternion.LookRotation(A, B).
A being a vector to which Z will be colinear, X being orthogonal to the plane defined by A and B, and Y belonging to that plane.
Followup:
To match other axis to A, there are multiple solutions. First would be to simply apply the lookRotation to a parent object, while the child object is rotated inside to match whatever rotation you want. You can also edit your entire mesh to do so.
The other way is to simply apply another rotation, so that both combined get your desired result, like so:
Quaternion zMatched = Quaternion.LookRotation(zAxisTarget, direction)
Quaternion yMatched = zMatched * Quaternion.AngleAxis(90f, Vector3.right);
transform.rotation = yMatched;
This will rotate the object so that the y axis becomes collinear to the previous z axis.
This is however nor perfect. If you reach this point, you should consider building your own clearer solution based on combining AngleAxis results. But it works well enough.
I have two identical objects and two cameras. What I'd like to achieve is the following:
1) Capture the position and rotation of Cube A relative to Camera A
2) Transfer that to a Cube B, so that in Camera B (which I cannot move), Cube B looks exactly as Cube A looks in Camera A
I was successful doing that with position with the following code:
positionDifference = CubeA.InverseTransformPoint(CameraA.transform.position);
To transfer it onto Cube B, I do:
cubeBpos = transform.InverseTransformPoint(CubeB.transform.localPosition);
while ( Mathf.Abs (cubeBpos.x + positionDifference.x) > 0.01f ) {
if (cubeBpos.x + positionDifference.x > 0) {
CubeB.transform.position += new Vector3(-0.01f, 0, 0);
}
else if (cubeBpos.x + positionDifference.x < 0) {
CubeB.transform.position += new Vector3(+0.01f, 0, 0);
}
cubeBpos = transform.InverseTransformPoint(CubeB.transform.position);
}
That's clunky, but works. However, when I try to transfer rotations, Cube B starts to pivot around the origin. Interestingly, when I move Cube A in world coordinates, Cube B moves in local, and vice versa. I suspect local-to-world coordinate translation is an issue, but I also think my rotation code is naive. I tried to capture rotation in two ways, first like this:
rotationDifference = Quaternion.Inverse(CubeA.transform.rotation) * CameraA.transform.rotation;
CubeB.transform.rotation = Quaternion.Inverse(rotationDifference);
Second attempt:
rotationDifference = new Vector3(transform.eulerAngles.x, transform.eulerAngles.y, transform.eulerAngles.z);
CubeB.transform.eulerAngles = rotationDifference;
Both approaches resulted in weird rotational offsets. I tried using localPosition and localEulerAngles, didn't help.
I'm hoping there's a smarter way to do this :)
EDIT:
Here's a Dropbox link to the project
The problem is that you are treating position and rotation separately although they influence each other. Let's put both together and say we have model transforms for the two cameras and the two cubes (represented as homogeneous matrices; assuming column vectors). Then, we want to find the transform for cube B TCubeB, such that:
TCameraA^-1 * TCubeA = TCameraB^-1 * TCubeB
Note that TCamera is the model transform of the camera, not the view matrix. If you have the view matrix, simply leave the inverse away.
We can immediately solve for TCubeB:
TCameraB * TCameraA^-1 * TCubeA = TCubeB
I'm not too familiar with the Unity API but it seems like you cannot use transformation matrices directly. So let's split the transformation matrix T in a rotational part R and a translational part Tr:
TrCameraB * RCameraB * (TrCameraA * RCameraA)^-1 * TrCubeA * RCubeA = TrCubeB * RCubeB
TrCameraB * RCameraB * RCameraA^-1 * TrCameraA^-1 * TrCubeA * RCubeA = TrCubeB * RCubeB
If we care only about the rotation, we can calculate the respective quaternion by simply doing:
QCameraB * QCameraA^-1 * QCubeA = QCubeB
The translation becomes a bit more difficult. We need to find the translation transform, such that
TrCameraB * RCameraB * RCameraA^-1 * TrCameraA^-1 * TrCubeA * RCubeA * RCubeB^-1 = TrCubeB
To find the translation vector, simply multiply the origin to the left-hand side:
TrCameraB * RCameraB * RCameraA^-1 * TrCameraA^-1 * TrCubeA * RCubeA * RCubeB^-1 * (0, 0, 0, 1)
In pseudo-code, this boils down to (the matrices that appear stand for the respective translation vectors):
Vector4 translation = (0, 0, 0, 1)
translation += TrCubeA
translation -= TrCameraA
translation = RCameraA.Inverse().Transform(translation)
translation = RCameraB.Transform(translation)
translation += TrCameraB
Again, I barely know the Unity API and it might use some different conventions than I did (conventions in transformation math are especially tricky). But I am sure you can adapt the above derivations if something is not correct.
Nico's great answer solved my issue, but since code-formatting in the comment section wasn't built for this, here's the code that I wrote for Unity based on Nico's answer:
Vector3 translation = new Vector3(0,0,0);
translation += cubeA.transform.position;
translation -= cameraA.transform.position;
translation = cameraA.transform.InverseTransformPoint(translation);
translation = cameraB.transform.TransformPoint(translation/2);
cubeB.transform.position = translation;
Quaternion rotation = Quaternion.identity;
rotation = cameraB.transform.rotation * Quaternion.Inverse(cameraA.transform.rotation) * cubeA.transform.rotation;
cubeB.transform.rotation = rotation;
It's not one-to-one, but it achieves what I hoped: If Cube A is stationary and Camera A moves around it, then Cube B moves with respect to Camera B in such a way that Cube A and Cube B always have the exact same position and rotation in relation to their respective cameras.
I am encountering an issue when updating a bullet's position when I rotate the ship that shoots it. Currently the code almost works except that at most angles the bullet is not fired from where i want it to. Sometimes it fires slightly upward than the centre where it should be, and sometimes downwards. I'm pretty sure it has something to do with the trigonometry but I'm having a hard time finding the problem.
This code is in the constructor; it sets the bullet's initial position and rotation based on the ship's position (sp.position) and its rotation (Controls.rotation):
this.backgroundRect = new RotatedRectangle(Rectangle.Empty, Controls.rotation);
//get centre of ship
this.position = new Vector2(sp.getPosition().X + sp.getTexture().Width/2,
(sp.getPosition().Y + sp.getTexture().Height / 2));
//get blast pos
this.position = new Vector2((float)(this.position.X + (sp.getTexture().Width / 2 *
Math.Cos(this.backgroundRect.Rotation))), (float)(this.position.Y +
(sp.getTexture().Width / 2 * Math.Sin(this.backgroundRect.Rotation))));
//get blast pos + texture height / 2
this.position = new Vector2((float)(this.position.X + (this.texture.Height / 2 *
Math.Cos(this.backgroundRect.Rotation))), (float)(this.position.Y +
(this.texture.Height / 2 * Math.Sin(this.backgroundRect.Rotation))));
this.backgroundRect = new RotatedRectangle(new Rectangle((int)this.position.X,
(int)this.position.Y, this.texture.Width, this.texture.Height), Controls.rotation);
speed = defSpeed;
Then in the update method I use the following code; I'm pretty sure this is working fine though:
this.position.X = this.position.X + defSpeed *
(float)Math.Cos(this.getRectangle().Rotation);
this.position.Y = this.position.Y + defSpeed *
(float)Math.Sin(this.getRectangle().Rotation);
this.getRectangle().CollisionRectangle.X = (int)(position.X + defSpeed *
(float)Math.Cos(this.getRectangle().Rotation));
this.getRectangle().CollisionRectangle.Y = (int)(position.Y + defSpeed *
(float)Math.Sin(this.getRectangle().Rotation));
Also I should mention that my ship had not been working correctly when I rotated it since the origin (center of rotation) was (0,0). To remedy this I changed the origin to the center of the ship (width/2,height/2) and also added those values to the drawing rectangle so that the sprite would draw correctly. I imagine this could be the problem and suppose there's a better way of going around this. The game is in 2D by the way.
The problem is that the sprites are not drawn on the positions they are supposed to be. When calculating the center position, you assume that the sprite is not rotated. This causes some trouble. Furthermore, rotating an arbitrary vector is not as easy as you did. You can only rotate a vector by multiplying its components with sin/cos when it is on the x-axis.
Here is how you can rotate arbitrary vectors:
Vector2 vecRotate(Vector2 vec, double phi)
{
double length = vec.Length(); //Save length of vector
vec.Normalize();
double alpha = Math.Acos(vec.X); //Angle of vector against x-axis
if(vec.Y < 0)
alpha = 2 * Math.PI - alpha
alpha += phi;
vec.X = Math.Cos(alpha) * length;
vec.Y = Math.Sin(alpha) * length;
return vec;
}
Make sure, the angle is in radians.
With that you can calculate the correct position of the ship:
Vector2 ShipCenter = sp.Position() + vecRotate(new Vector2(sp.getTexture().Width/2, sp.getTexture().Height/2), Controls.Rotation);
You can use the same function to determine the position of the bullet's center. I don't exactly know, where you want to place the bullet. I will assume, it is at the center of the right edge:
Vector2 offset = new Vector2(sp.getTexture.Width/2, 0);
this.position = ShipCenter + vecRotate(offset, Controls.Rotation);
If you then need the position of the upper left corner of the bullet, you can go further:
this.position -= vecRotate(new Vector2(this.texture.Width/2, this.texture.Height/2), Controls.Rotation)
However, as I stated, it is probably easier to handle the ship's and bullets' positions as central positions. Doing so, you need far less trigonometry.
This is my Transform. I got it from an example of a simple 2D camera.
public Matrix Transform(GraphicsDevice graphicsDevice)
{
float ViewportWidth = graphicsDevice.Viewport.Width;
float ViewportHeight = graphicsDevice.Viewport.Height;
matrixTransform =
Matrix.CreateTranslation(new Vector3(-cameraPosition.X, -cameraPosition.Y, 0)) *
Matrix.CreateRotationZ(Rotation) *
Matrix.CreateScale(new Vector3(Zoom, Zoom, 0)) *
Matrix.CreateTranslation(
new Vector3(ViewportWidth * 0.5f, ViewportHeight * 0.5f, 0));
return matrixTransform;
}
If I understand it correctly, it allows for a roll(rotation), sprite scale change on zoom, and translation between world and camera for simple up, down, left, right controls. However, it does not alter the Z depth.
But what I need is for the game world to zoom, not just the sprites drawn. And I assume in order to do this I need to change the Z distance between the camera and the world matrix.
I am VERY NEW to programming and have only a simple understanding of matrix in general. I have even less understanding as to how XNA uses them in the draw method. And so far I feel like pulling my hair out from a fruitless search for answers... I just need the world coordinates to scale on zoom, so that before my mouse at a pre-zoom X.60 Y.60 will be at X.600 Y.600 post-zoom (ie: zoom level 0.1). But my mouse has not moved, only the world got bigger in view (or shrank).
I know this question is old, but this is in case anyone comes across this problem and can't find a solution. #RogueDeus was trying to convert scaled input coordinates when he was zooming in or out with his camera. In order to scale the mouse, all you need is to get the inverse matrix of the scale.
So if his scale matrix was created as this:
Matrix.CreateScale(zoom, zoom, 0);
The mouse coordinates should be inverse scaled and shifted by the necessary translation:
float ViewportWidth = graphicsDevice.Viewport.Width;
float ViewportHeight = graphicsDevice.Viewport.Height;
Matrix scale = Matrix.CreateScale(zoom, zoom, 0);
Matrix inputScalar = Matrix.Invert(scale);
...
public MouseState transformMouse(MouseState mouse)
{
/// Shifts the position to 0-relative
Vector2 newPosition = new Vector2(mouse.X - ViewportWidth,
mouse.Y - ViewportHeight);
/// Scales the input to a proper size
newPosition = Vector2.Transform(newPosition, InputScalar);
return new MouseState((int)newPosition.X, (int)newPosition.Y,
mouse.ScrollWheelValue, mouse.LeftButton,
mouse.MiddleButton, mouse.RightButton,
mouse.XButton1, mouse.XButton2);
}
You are using 2D coordinates, therefore the Z coordinate is of absolutely no importance. In fact, the scale matrix you are using ( Matrix.CreateScale(new Vector3(Zoom, Zoom, 0)) ) multiply the Z coordinate by 0, effectively setting it to 0.
As this scale matrix is in the view matrix, it will scale the entire world. I am not sure to really understand your problem. Could you try to explain it a litle more, please?
I seem to have figured out how to get the coordinates to scale...
I was assuming that the current mouse status would reflect the world matrix its clicked on, but apparently it never actually does this. It is always linked to the view matrix. (The screen itself) and that value needs to scale along with the world matrix (in the transform).
So as the transform is effected by Zoom in the Matrix.CreateScale(new Vector3(Zoom, Zoom, 0)) so too does the mouseState X & Y coordinates need to be scaled by it to virtually mirror the world matrix.
//Offsets any cam location by a zoom scaled window bounds
Vector2 CamCenterOffset
{
get { return new Vector2((game.Window.ClientBounds.Height / Zoom)
* 0.5f, (game.Window.ClientBounds.Width / Zoom) * 0.5f);
}
}
//Scales the mouse.X and mouse.Y by the same Zoom as everything.
Vector2 MouseCursorInWorld
{
get
{
currMouseState = Mouse.GetState();
return cameraPosition + new Vector2(currMouseState.X / Zoom,
currMouseState.Y / Zoom);
}
}
I know this is probably a very simple question, but I can't seem to figure it out. First of all, I want to specify that I did look over Google and SO for half an hour or so without finding the answer to my question(yes, I am serious).
Basically, I want to rotate a Vector2 around a point(which, in my case, is always the (0, 0) vector). So, I tried to make a function to do it with the parameters being the point to rotate and the angle(in degrees) to rotate by.
Here's a quick drawing showing what I'm trying to achieve:
I want to take V1(red vector), rotate it by an angle A(blue), to obtain a new vector (V2, green). In this example I used one of the simplest case: V1 on the axis, and a 90 degree angle, but I want the function to handle more "complicated" cases too.
So here's my function:
public static Vector2 RotateVector2(Vector2 point, float degrees)
{
return Vector2.Transform(point,
Matrix.CreateRotationZ(MathHelper.ToRadians(degrees)));
}
So, what am I doing wrong? When I run the code and call this function with the (0, -1) vector and a 90 degrees angle, I get the vector (1, 4.371139E-08) ...
Also, what if I want to accept a point to rotate around as a parameter too? So that the rotation doesn't always happen around (0, 0)...
Chris Schmich's answer regarding floating point precision and using radians is correct. I suggest an alternate implementation for RotateVector2 and answer the second part of your question.
Building a 4x4 rotation matrix to rotate a vector will cause a lot of unnecessary operations. The matrix transform is actually doing the following but using a lot of redundant math:
public static Vector2 RotateVector2(Vector2 point, float radians)
{
float cosRadians = (float)Math.Cos(radians);
float sinRadians = (float)Math.Sin(radians);
return new Vector2(
point.X * cosRadians - point.Y * sinRadians,
point.X * sinRadians + point.Y * cosRadians);
}
If you want to rotate around an arbitrary point, you first need to translate your space so that the point to be rotated around is the origin, do the rotation and then reverse the translation.
public static Vector2 RotateVector2(Vector2 point, float radians, Vector2 pivot)
{
float cosRadians = (float)Math.Cos(radians);
float sinRadians = (float)Math.Sin(radians);
Vector2 translatedPoint = new Vector2();
translatedPoint.X = point.X - pivot.X;
translatedPoint.Y = point.Y - pivot.Y;
Vector2 rotatedPoint = new Vector2();
rotatedPoint.X = translatedPoint.X * cosRadians - translatedPoint.Y * sinRadians + pivot.X;
rotatedPoint.Y = translatedPoint.X * sinRadians + translatedPoint.Y * cosRadians + pivot.Y;
return rotatedPoint;
}
Note that the vector arithmetic has been inlined for maximum speed.
So, what am I doing wrong? When I run the code and call this function with the (0, -1) vector and a 90 degrees angle, I get the vector (1, 4.371139E-08) ...
Your code is correct, this is just a floating point representation issue. 4.371139E-08 is essentially zero (it's 0.0000000431139), but the transformation did not produce a value that was exactly zero. This is a common problem with floating point that you should be aware of. This SO answer has some additional good points about floating point.
Also, if possible, you should stick with radians instead of using degrees. This is likely introducing more error into your computations.