I'm having a problem in Monogame/XNA where I am storing the position/rotation/scale in a matrix for easier manipulation of these values with parenting. It all seems to be going well, except for when I rotate the matrix, which is when the object seems to jitter around and shake. Here is the relevant code:
// Executes every frame
internal void Draw()
{
float RadRot = MathHelper.ToRadians(Rotation);
// Sets world matrix for things that draw
if (Parent != null) {
WorldMatrix = Matrix.CreateScale(Size.X, Size.Y, 1) * -Matrix.CreateTranslation(Pivot.X, Pivot.Y, 0) * Matrix.CreateRotationZ(RadRot);
WorldMatrix.Translation = Vector3.Zero; // Rotating seems to create a strange positioning issue, this fixes it
WorldMatrix *= Matrix.CreateTranslation(RelativePosition.X, RelativePosition.Y, 0) * Matrix.CreateRotationZ(MathHelper.ToRadians(Parent.Rotation)) * <FIX JITTER> * Matrix.CreateTranslation(Parent.DecomposedPosition.X, Parent.DecomposedPosition.Y, 0);
} else
{
// For objects without parents
WorldMatrix = Matrix.CreateScale(RelativeSize.X, RelativeSize.Y, 1) * -Matrix.CreateTranslation(Pivot.X, Pivot.Y, 0) * Matrix.CreateRotationZ(RadRot);
WorldMatrix.Translation = Vector3.Zero; // This fixes the jitter issue, but in the code above (in the if statement), I can't do this when translating the child around the parent
WorldMatrix *= Matrix.CreateTranslation(RelativePosition.X, RelativePosition.Y, 0);
}
Vector3 TempPos = Vector3.Zero;
Quaternion TempRot = Quaternion.Identity;
Vector3 TempSize = Vector3.Zero;
WorldMatrix.Decompose(out TempSize, out TempRot, out TempPos);
DecomposedPosition = new Vector2(-TempPos.X, -TempPos.Y);
DecomposedRotation = MathHelper.ToRadians(Rotation); // Can't convert quaternions, but this works
DecomposedSize = new Vector2(MathF.Round(TempSize.X), MathF.Round(TempSize.Y));
foreach (Component2D comp in Components)
{
comp.Draw();
}
}
This is what comp.Draw calls:
MyGame.SpriteDrawing.Draw(Image, new Rectangle(LinkedObject.DecomposedPosition.ToPoint(), LinkedObject.DecomposedSize.ToPoint()), RenderRegion, Color, LinkedObject.DecomposedRotation, LinkedObject.Pivot, SpriteEffects.None, LinkedObject.Layer + (SubLayer / 10000));
Here is what it looks like in action:
EXTRA NOTE: This jittering does occur no matter the scale of the objects
Give it a try to organize the scene in a different manner:
Each object let store its scale (Vector3), rotation (Quaternion), and position (Vector3)
Each object should have a property of result matrix (in local space), which will be the multiplication of something like this in following order: Matrix.CreateScale(scale) * Matrix.CreateRotationQuaterion(rotation) * Matrix.CreateTranslation(position)
Now, when you have the above local space Matrix, use it to create WorldMatrix, which is a multiplication of Parent World Matrix and this object local space Matrix, or local space Matrix only - when no Parent is present.
Try to pass only the world matrix of each element to your drawing code, avoid decomposition
If you have a large hierarchy, you could cache local matrix results if it is not changed
It seems that I was simply using the wrong SpriteBatch.Draw(). I was using the Rectangle version, whereas all I needed was to use the Vector2 version to prevent rounding errors.
MyGame.SpriteDrawing.Draw(Image, LinkedObject.DecomposedPosition, RenderRegion, Color, LinkedObject.DecomposedRotation, LinkedObject.Pivot * RenderRegion.Size.ToVector2(), LinkedObject.DecomposedSize / RenderRegion.Size.ToVector2(), SpriteEffects.None, LinkedObject.Layer + (SubLayer / 10000));
Related
So, I wanted my gameObject to rotate around the y axis until it reached a certain number, but I'm unsure how to procceed.
void Update()
{
if (target == 1) {
this.transform.position += new Vector3(1,0,1) * Time.deltaTime * speed;
// this next part is where I'm lost:
if (this.transform.rotation.eulerAngles.y < 30f) {
this.transform.Rotate.eulerAngles += (0,1,0, Space.Self);
}
}
Rotate is a method and you would use it like
void Update()
{
if (target == 1)
{
transform.position += new Vector3(1,0,1) * Time.deltaTime * speed;
if (this.transform.rotation.eulerAngles.y < 30f)
{
this.transform.Rotate(0, 1 * Time.deltaTime, 0);
}
}
Note that I changed two things:
your code is frame-rate dependent! Rather multiply the amount of rotation (just as you did with the position) by Time.deltaTime in order to convert it from value/frame into value/second. Of course this currently would rotate with only 1°/second .. you probably want something faster
Space.Self is the default value for the optional parameter space so you only have to pass it in if you want to use Space.World
However, there is another flaw
eulerAngles shouldn't be used at all for assigning or checking continously updated values!
When using the .eulerAngles property to set a rotation, it is important to understand that although you are providing X, Y, and Z rotation values to describe your rotation, those values are not stored in the rotation. Instead, the X, Y & Z values are converted to the Quaternion's internal format.
When you read the .eulerAngles property, Unity converts the Quaternion's internal representation of the rotation to Euler angles. Because, there is more than one way to represent any given rotation using Euler angles, the values you read back out may be quite different from the values you assigned. This can cause confusion if you are trying to gradually increment the values to produce animation. See bottom scripting example for more information.
You might want to consider to rather pre-calculate the target rotation as a Quaternion e.g. like
Quaternion targetRotation;
private void Start ()
{
targetRotation = Quaternion.Euler(0, 30, 0);
}
and then later rather use e.g. Quaternion.RotateTowards
transform.localRotation = Quaternion.RotateTowards(transform.localRotation, targetRotation, Time.deltaTime * rotationAbglesPerSecond);
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 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.
I am working on a project which has following goal:
Load rigged 3D mesh (e.g. a human skeleton) with Assimp.NET
Manipulate bones of mesh so it fits your own body (with Microsoft Kinect v2)
Perform vertex skinning
Loading the rigged mesh and extracting bone information works (hopefully) without any problems (based on this tutorial: http://www.richardssoftware.net/2013/10/skinned-models-in-directx-11-with.html). Each bone (class "ModelBone") consists of following information:
Assimp.Matrix4x4 LocalTransform
Assimp.Matrix4x4 GlobalTransform
Assimp.Matrix4x4 Offset
LocalTransform is directly extracted from assimp node (node.Transform).
GlobalTransform includes own LocalTransform and all parent's LocalTransform (see code snipped calculateGlobalTransformation()).
Offset is directly extracted from assimp bone (bone.OffsetMatrix).
At the moment I don't have GPU vertex skinning implemented, but I iterate over each vertex and manipulate it's position and normal vector.
foreach (Vertex vertex in this.Vertices)
{
Vector3D newPosition = new Vector3D();
Vector3D newNormal = new Vector3D();
for (int i=0; i < vertex.boneIndices.Length; i++)
{
int boneIndex = vertex.boneIndices[i];
float boneWeight = vertex.boneWeights[i];
ModelBone bone = this.BoneHierarchy.Bones[boneIndex];
Matrix4x4 finalTransform = bone.GlobalTransform * bone.Offset;
// Calculate new vertex position and normal
newPosition += boneWeight * (finalTransform * vertex.originalPosition);
newNormal += boneWeight * (finalTransform * vertex.originalNormal);
}
// Apply new vertex position and normal
vertex.position = newPosition;
vertex.normal = newNormal;
}
Like I already said, I want to manipulate bones with a Kinect v2 sensor, so I won't have to use animations (e.g. interpolating keyframes, ...)! But for the beginning I want to be able to manipulate bones manually (e.g. rotate torso of mesh by 90 degrees). Therefore I create a 4x4 rotation matrix (90 degrees around x-axis) by calling Assimp.Matrix4x4.FromRotationX(1.5708f);. Then I replace the bone's LocalTransform with this rotation matrix:
Assimp.Matrix4x4 rotation = Assimp.Matrix4x4.FromRotationX(1.5708f);
bone.LocalTransform = rotation;
UpdateTransformations(bone);
After the bone manipulation I use following code to calculate the new GlobalTransform of the bone and it's child bones:
public void UpdateTransformations(ModelBone bone)
{
this.calculateGlobalTransformation(bone);
foreach (var child in bone.Children)
{
UpdateTransformations(child);
}
}
private void calculateGlobalTransformation(ModelBone bone)
{
// Global transformation includes own local transformation ...
bone.GlobalTransform = bone.LocalTransform;
ModelBone parent = bone.Parent;
while (parent != null)
{
// ... and all local transformations of the parent bones (recursively)
bone.GlobalTransform = parent.LocalTransform * bone.GlobalTransform;
parent = parent.Parent;
}
}
This approach results in this image. The transformation seems to be applied correctly to all child bones, but the manipulated bone rotates around the world space origin and not around its own local space :( I already tried to include the GlobalTransform translation (last row of GlobalTransform) into the rotation matrix before set it as the LocalTransform, but without success...
I hope somebody can help me with this problem!
Thanks in advance!
Finally I found the solution :) All calculations were correct except:
Matrix4x4 finalTransform = bone.GlobalTransform * bone.Offset;
The correct calculation for me is:
Matrix4x4 finalTransform = bone.GlobalTransform * bone.Offset;
finalTransform.transpose();
So it seems to be a row-major / column-major problem. My final CPU vertex skinning code is:
public void PerformSmoothVertexSkinning()
{
// Precompute final transformation matrix for each bone
List<Matrix4x4> FinalTransforms = new List<Matrix4x4>();
foreach (ModelBone bone in this.BoneHierarchy.Bones)
{
// Multiplying a vector (e.g. vertex position/normal) by finalTransform will (from right to left):
// 1. transform the vector from mesh space to bone space (by bone.Offset)
// 2. transform the vector from bone space to world space (by bone.GlobalTransform)
Matrix4x4 finalTransform = bone.GlobalTransform * bone.Offset;
finalTransform.Transpose();
FinalTransforms.Add(finalTransform);
}
foreach (Submesh submesh in this.Submeshes)
{
foreach (Vertex vertex in submesh.Vertices)
{
Vector3D newPosition = new Vector3D();
Vector3D newNormal = new Vector3D();
for (int i = 0; i < vertex.BoneIndices.Length; i++)
{
int boneIndex = vertex.BoneIndices[i];
float boneWeight = vertex.BoneWeights[i];
// Get final transformation matrix to transform each vertex position
Matrix4x4 finalVertexTransform = FinalTransforms[boneIndex];
// Get final transformation matrix to transform each vertex normal (has to be inverted and transposed!)
Matrix4x4 finalNormalTransform = FinalTransforms[boneIndex];
finalNormalTransform.Inverse();
finalNormalTransform.Transpose();
// Calculate new vertex position and normal (average of influencing bones)
// Formula: newPosition += boneWeight * (finalVertexTransform * vertex.OriginalPosition);
// += boneWeight * (bone.GlobalTransform * bone.Offset * vertex.OriginalPosition);
// From right to left:
// 1. Transform vertex position from mesh space to bone space (by bone.Offset)
// 2. Transform vertex position from bone space to world space (by bone.GlobalTransform)
// 3. Apply bone weight
newPosition += boneWeight * (finalVertexTransform * vertex.OriginalPosition);
newNormal += boneWeight * (finalNormalTransform * vertex.OriginalNormal);
}
// Apply new vertex position and normal
vertex.Position = newPosition;
vertex.Normal = newNormal;
}
}
}
Hopefully this thread is helpful for other people. Thanks for your help Sergey!
To transform a bone you should use it's offset matrix:
http://assimp.sourceforge.net/lib_html/structai_bone.html#a9ae5293b5c937436e4b338e20221cc2e
Offset matrix transforms from global space to bone space. If you want to rotate a bone around it's origin you should:
transform to bone space
apply rotation
transform to global space
So bone's global transform may be calculated like this:
bonesGlobalTransform = parentGlobalTransform *
bone.offset.inverse() *
boneLocalTransform *
bone.offset;
So:
transform to bone space with offset matrix
apply local transform
transform to global space with offset.inverse() matrix