I thought I understood matrix math well enough, but apparently I'm clueless
Here's the setup:
I have an object at [0,0,0] in world space. I have a camera class controlled by mouse movements to rotate and zoom around the object such that it always looks at it. Here is how I calculate my viewMatrix from the camera:
public Matrix viewMatrix {
get {
return
Matrix.CreateFromAxisAngle(Vector3.Up, rotAngle)
* Matrix.CreateFromAxisAngle(Vector3.Left, pitchAngle)
* Matrix.CreateTranslation(0, 0, distance)
;
}
}
I need to be able to get the position of the camera in world space so I can get its distance from the box--particularly its distance from each face of the box. How can I get the camera's xyz position in world space coords?
I've tried:
// all of these only return [0, 0, distance];
Vector3 pos = Vector3.Transform(Vector3.Zero, viewMatrix);
Vector3 pos = viewMatrix.Translation;
Vector3 pos = new Vector3(viewMatrix.M41, viewMatrix.M42, viewMatrix.M43);
It seems like the rotation information is being lost somehow. The strange thing is that the viewMatrix code works perfectly for positioning the camera!
or to simplify slightly:
Vector3 pos = Matrix.Invert(view).Translation;
Once again, I figure out the problem within seconds of posting the question:
I needed to invert the view matrix. The rotation info was being lost because it plays no part in the distance calculation until the view matrix is inverted. The rotation was at the wrong "end" of the transformation.
Vector3 pos = Vector3.Transform(Vector3.Zero, Matrix.Invert(viewMatrix));
Related
I meanwhile found a solution, but I'd really like a code review. Perhaps there is an easier solution. I updated the script at the end of my question.
I have a camera in my scene with a specific position and rotation. I want to move the camera on the x- and z-axis so that it targets (0,0,0).
I have the position vector posVector of the camera object and the displacement vector dirVector, which is the viewing direction. Now at some point, posVector + dirVector will intersection the ground plane. This currently is (x,0,z) but I like to move the camera on the x- and z-axis so that this is (0,0,0). So I really need to know x and z.
I tried multiple things, but I still have trouble wrapping my head around vectors and intersections.
using UnityEngine;
[ExecuteInEditMode]
public class DebugHelper : MonoBehaviour {
public Camera mainCamera;
// This DebugHelper script runs in EditMode and will help me visualize vectors and also move the camera to where I want it to be.
private void Update()
{
Vector3 posVector = mainCamera.transform.position;
Vector3 dirVector = mainCamera.transform.rotation * Vector3.forward;
// Find scalar for dirVector that displaces posVector.y to 0
// posVector + dirVector * scalar = (x,0,z)
float scalar = Mathf.Abs(posVector.y / dirVector.y);
// Get the position vector of the current camera target at y=0.
Vector3 y0TargetVector = posVector + dirVector * scalar;
// Subtract the y0TargetVector from the posVector to move it back to zero. This works because y0TargetVector.y is 0 and therefore the height is the same.
mainCamera.transform.position -= y0TargetVector ;
Debug.DrawRay(posVector, dirVector * scalar, new Color(0, 1, 0));
}
}
I have an image UI in a canvas with Screen Space - Camera render mode. What I like to do is move my LineRenderer to the image vertical position by looping through all the LineRenderer positions and changing its y axis. My problem is I cant get the correct position of the image that the LineRenderer can understand. I've tried using ViewportToWorldPoint and ScreenToWorldPoint but its not the same position.
Vector3 val = Camera.main.ViewportToWorldPoint(new Vector3(image.transform.position.x, image.transform.position.y, Camera.main.nearClipPlane));
for (int i = 0; i < newListOfPoints.Count; i++)
{
line.SetPosition(i, new Vector3(newListOfPoints[i].x, val.y, newListOfPoints[i].z));
}
Screenshot result using Vector3 val = Camera.main.ScreenToWorldPoint(new Vector3(image.transform.localPosition.x, image.transform.localPosition.y, -10));
The green LineRenderer is the result of changing the y position. It should be at the bottom of the square image.
Wow, this was annoying and complicated.
Here's the code I ended up with. The code in your question is the bottom half of the Update() function. The only thing I changed is what was passed into the ScreenToWorldPoint() method. That value is calculated in the upper half of the Update() function.
The RectTransformToScreenSpace() function was adapted from this Unity Answer post1 about getting the screen space coordinates of a RectTransform (which is exactly what we want in order to convert from screen space coordinates back into world space!) The only difference is that I was getting inverse Y values, so I changed from Screen.height - transform.position.y to just transform.position.y which did the trick perfectly.
After that it was just a matter of grabbing that rectangle's lower left corner, making it a Vector3 instead of a Vector2, and passing it back into ScreenToWorldPoint(). The only trick there was because of the perspective camera, I needed to know how far away the line was from the camera originally in order to maintain that same distance (otherwise the line moves up and down the screen faster than the image). For an orthographic camera, this value can be anything.
void Update () {
//the new bits:
float dist = (Camera.main.transform.position - newListOfPoints[0]).magnitude;
Rect r = RectTransformToScreenSpace((RectTransform)image.transform);
Vector3 v3 = new Vector3(r.xMin, r.yMin, dist);
//more or less original code:
Vector3 val = Camera.main.ScreenToWorldPoint(v3);
for(int i = 0; i < newListOfPoints.Count; i++) {
line.SetPosition(i, new Vector3(newListOfPoints[i].x, val.y, newListOfPoints[i].z));
}
}
//helper function:
public static Rect RectTransformToScreenSpace(RectTransform transform) {
Vector2 size = Vector2.Scale(transform.rect.size, transform.lossyScale);
Rect rect = new Rect(transform.position.x, transform.position.y, size.x, size.y);
rect.x -= (transform.pivot.x * size.x);
rect.y -= ((1.0f - transform.pivot.y) * size.y);
return rect;
}
1And finding that post from a generalized search on "how do I get the screen coordinates of a UI object" was not easy. A bunch of other posts came up and had some code, but none of it did what I wanted (including converting screen space coordinates back into world space coordinates of the UI object which was stupid easy and not reversibe, thanks RectTransformUtility!)
I'm trying to implement centripetal force in a programming language.
I saw some videos teaching the theory. But I dont know how to apply that in a programming language.
If I understand I have to apply centripetal force ac = v²/r to the velocity vector. But I dont know exactly how to proceed.
I have two game objects, one depicting Earth, other depicting Moon. What I wanted is to translate the moon around earth and using a button to "cut/cancel" the centripetal force in order to the moon get out to the earth's orbit.
I have no clue how to start that.
All I know is to rotate like this:
velocity.x = Mathf.Cos(Time.time) * earth_moon_radius;
velocity.z = Mathf.Sin(Time.time) * earth_moon_radius;
moon.transform.position = velocity;
But how to apply centripetal force as described above?
If you just want to have the moon rotating around earth and some trigger to release the moon, it's easier to use rotation around a center instead of forces. Given the following GameObject hierarchy:
Center (MoonRotator attached)
-- Moon
-- Earth
public class MoonRotator : MonoBehaviour
{
public bool cancelCentripetalForce = false;
public Vector3 angularVelocity = new Vector3 (0f, 0f, 100f);
public GameObject moon;
void Update () {
if (cancelCentripetalForce) {
Vector3 radius = moon.transform.position - transform.position;
Vector3 angularVelocityRadians = Mathf.Deg2Rad * angularVelocity;
moon.rigidbody.velocity = Vector3.Cross (angularVelocityRadians, radius);
moon.transform.parent = null;
Destroy (this);
} else {
Vector3 rot = transform.rotation.eulerAngles + angularVelocity * Time.deltaTime;
transform.rotation = Quaternion.Euler (rot);
}
}
}
If cancelCentripetalForce is set true Moon stops travelling around earth but proceeds with its current tangential velocity. This is given as:
v = ω × r
Earth has localPosition (0, 0, 0) and Moon is in this example located in the x-y plane rotating around the z axis.
If you want to cancel the force, add an opposing force vector that is based on your object's linear velocity and current direction.
So I have an object pointing straight along the Z axis. The object's 'forward' vector is 0, 0, 1. Do 1 - Math.abs(forward.x), same for y and z to get 1, 1, 0 when you're pointing forward along the Z axis. You want the direction you're pointing in to be 0'd so that the inertia from that direction is not damped in any way. Now you can apply a cancellation force in any direction that you are NOT pointed in.
This means that if your object is moving in any direction in world space that it's not facing in you can easily apply a cancellation force that is multiplied by the object's linear velocity to get circular motion that uses forces instead of directly setting velocity.
Another way you can do it is solve for V by manually setting it, then find radius using V=RW. You should know V and R or W. Now you can find the force necessary to keep the orbit stable around a point, rather than adding inertia from every frame.
I'm working on a 3D XNA project, and I've been thinking about this problem for like 2 weeks.
So I just decided to ask you.
Basically I have a flat plane and i want to project the mouse position to that plane, but how?
I tried many ways to do it, calculated angles...
But i figured out, that the distance must effect on the X position, maybe some math is needed what I've never heard before.
I did some code few years ago which returns the position as Vector3(x,y,z), given mouse state:
private Vector3 FindWhereClicked(MouseState ms)
{
Vector3 nearScreenPoint = new Vector3(ms.X, ms.Y, 0);
Vector3 farScreenPoint = new Vector3(ms.X, ms.Y, 1);
Vector3 nearWorldPoint = device.Viewport.Unproject(nearScreenPoint, cam.projectionMatrix, cam.viewMatrix, Matrix.Identity);
Vector3 farWorldPoint = device.Viewport.Unproject(farScreenPoint, cam.projectionMatrix, cam.viewMatrix, Matrix.Identity);
Vector3 direction = farWorldPoint - nearWorldPoint;
float zFactor = -nearWorldPoint.Y / direction.Y;
Vector3 zeroWorldPoint = nearWorldPoint + direction * zFactor;
return zeroWorldPoint;
}
device is an instance of GraphicsDevice.
Hope it works for you.
I'm programming a game in C# using the XNA3.1 engine. However I'm having a small issue with my camera, basically my camera tends to "flip" when it rotates more than 180 degrees on its roll (when the camera reaches 180 degrees, it seems to flip back to 0 degrees). The code for obtaining the view matrix is as follows:
Globals.g_GameProcessingInfo.camera.viewMat = Matrix.CreateLookAt(Globals.g_GameProcessingInfo.camera.target.pos, Globals.g_GameProcessingInfo.camera.LookAt, up); //Calculate the view matrix
The Globals.g_GameProcessingInfo.camera.LookAt variable the position 1 unit directly in front of the camera, relative to the rotation of the camera, and the "up" variable is obtained with the following function:
static Vector3 GetUp() //Get the up Vector of the camera
{
Vector3 up = Vector3.Zero;
Quaternion quat = Quaternion.Identity;
Quaternion.CreateFromYawPitchRoll(Globals.g_GameProcessingInfo.camera.target.rot.Y, Globals.g_GameProcessingInfo.camera.target.rot.X, Globals.g_GameProcessingInfo.camera.target.rot.Z, out quat);
up.X = 2 * quat.X * quat.Y - 2 * quat.W * quat.Z; //Set the up x-value based on the orientation of the camera
up.Y = 1 - 2 * quat.X * quat.Z - 2 * quat.Z * quat.Z; //Set the up y-value based on the orientation of the camera
up.Z = 2 * quat.Z * quat.Y + 2 * quat.W * quat.X; //Set the up z-value based on the orientation of the camera
return up; //Return the up Vector3
}
I got same problems in OpenGL with gluLookAt. I fixed that problem with my own camera class:
void Camera::ComputeVectors()
{
Matrix4x4 rotX, rotZ;
Quaternion q_x, q_y, q_z;
Quaternion q_yx, q_yz;
q_x.FromAngleAxis(radians.x, startAxisX);
q_y.FromAngleAxis(radians.y, startAxisY);
q_z.FromAngleAxis(radians.z, startAxisZ);
q_yx = q_y * q_x;
q_yx.ToMatrix(rotZ);
q_yz = q_y * q_z;
q_yz.ToMatrix(rotX);
axisX = startAxisX;
axisZ = startAxisZ;
axisX.Transform(rotX);
axisZ.Transform(rotZ);
axisY = axisX.Cross(axisZ);
position = startPosition;
position -= center;
position.Transform(q_yx);
position += center;
}
It is maybe overcomplicated, but working. axisY is your up vector.
Full code listing is at:
http://github.com/filipkunc/opengl-editor-cocoa/blob/master/PureCpp/MathCore/Camera.cpp
Hope it helps.
This is probably slower but the only way I know to do with would be the with the rotation matrix for 3D. Wikipedia Link
Where
and U = (Camera.position - Camera.lookat).norm
... Now, I believe that would give you the rotation part of the view matrix. However, I'm not 100% on it. I'm still looking into this though.
meh was hoping to see a tan in there somewhere.
can you link to where you got your equation from please?
(am at work and really don;t want to sit down myself and derive it)
how are you setting your camera rotation? are you sure nothing is going on there?
I'm a bit unsure about the math in your GetUp method. Could you elaborate on the math behind it?
In my lookat camera I initialize my up-vector once and then rotate that vector using a quaternion. This removes the possibility of trying to do a cross-product on parallel vectors in order to calculate the up vector.
Some semicode to clarify perhaps:
var up = Vector3.Up;
var target = <some point in space>;
var rotation = <current rotation quaternion>;
var forward = target - position;
forward = Vector3.Transform(forward, rotation);
var updatedPosition = target - forward;
var updatedUp = Vector3.Transform(up, rotation);
var view = Matrix.CreateLookAt(updatedPosition, target, updatedUp);
Since I wasn't satisfied with the answers here already, I had to figure this out myself.
What I discovered is it's actually quite simple. Just do this:
Matrix ypr = Matrix.CreateFromYawPitchRoll(yaw, pitch, roll);
Vector3 up = Vector3.Transform(Vector3.Up, ypr);
"up" is the direction you want.