Capture position and rotation of an object relative to another object - c#

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.

Related

Problems with buoyancy and multiple Gertsner-waves. Waves created using Shadergraph and equations recreated in code to try and simulate floatation

Quick summation:
I am attempting to create an ocean comprised of planes that can be easily loaded and unloaded based on distance. On this ocean I want a boat to sail with a player onboard in the first person, where I want them to experience the buoyancy of their boat relative to the surrounding waves.
I am new to shadergraph and have been following several tutorials to try and create the desired effect.
These tutorials include
Catlikecoding's Wave shader
https://catlikecoding.com/unity/tutorials/flow/waves/
Zicore's Gertsner wave
https://www.youtube.com/watch?v=Awd1hRpLSoI&ab_channel=Zicore
Tom Weiland's dynamic water physics
https://www.youtube.com/watch?v=eL_zHQEju8s&ab_channel=TomWeiland
These resources have gotten me a good chunk of the way there, but I've run into some issues regarding the boat physics specifically.
I understand the math behind simulating Gertsner waves, and have tried to set up a WaveManager that calculates the y-value of a "floater" at position (x,z).
Floater.cs
public Rigidbody rigidBody;
public float depthBeforeSubmerged = 1f;
public float displacementAmount = 3f;
public int floaterCount = 1;
public float waterDrag = 0.99f;
public float waterAngularDrag = 0.5f;
private void FixedUpdate()
{
rigidBody.AddForceAtPosition(Physics.gravity / floaterCount, transform.position, ForceMode.Acceleration);
float waveHeight = WaveManager.instance.GetWaveHeight(transform.position.x,transform.position.z);
if(transform.position.y < waveHeight)
{
float displacementMultiplier = Mathf.Clamp01((waveHeight-transform.position.y) / depthBeforeSubmerged) * displacementAmount;
rigidBody.AddForceAtPosition(new Vector3(0f, Mathf.Abs(Physics.gravity.y) * displacementMultiplier, 0f),transform.position, ForceMode.Acceleration);
rigidBody.AddForce(displacementMultiplier * -rigidBody.velocity * waterDrag * Time.fixedDeltaTime, ForceMode.VelocityChange);
rigidBody.AddTorque(displacementMultiplier * -rigidBody.angularVelocity * waterAngularDrag * Time.fixedDeltaTime, ForceMode.VelocityChange);
}
}
This is pretty much lifted directly from Tom Weiland's video. Basically, when my floatpoint dips below the calculated wave, it applies force to make it travel upwards. Following his instructions carefully yielded decent results, but the problem arose when I started using Shadergraph to create my ocean.
The main issue is I wanted the waves to be tileable across multiple planes, so I used the object position and transformed it to world position to do calculations, and then added it back to the object position before manipulating the vertices of the ocean plane.
I've tried to show it below here:
This makes the ocean plane tileable and looks great, but also enlarges it in the scene quite a bit. I've put a regular plane on top to show the difference. Both are 1x1 units in the inspector.
So this is the first problem. The calculations I do in my WaveManager aren't lining up properly with the actual visual representation of the waves.
The second problem is that I can't seem to make the calculations done in WaveManager give me the correct y-coordinates.
In the shader, the waves are animated using the Time-component.
I've found the documentation to be a bit sparse on Shadergraph components, probably because I'm self taught and have a hard time wrapping my head around some of these concepts.
I've had a hard time working out how to calculate the change in y-coordinates over time in the wavemanager-script. The different solutions I've tried have just made the y-coordinate slowly grow larger into the negative range. I just have no idea how to make my calculations match up with the ones done on the GPU.
It's no important that it be super accurate, just good enough to sell the effect with small waves.
The Wavemanager code, finally.
private void Start()
{
waveLengthA = waves.GetFloat("_WaveLengthA");
waveLengthB = waves.GetFloat("_WaveLengthB");
waveLengthC = waves.GetFloat("_WaveLengthC");
waveLengthD = waves.GetFloat("_WaveLengthD");
steepnessA = waves.GetFloat("_SteepnessA");
steepnessB = waves.GetFloat("_SteepnessB");
steepnessC = waves.GetFloat("_SteepnessC");
steepnessD = waves.GetFloat("_SteepnessD");
directionA = waves.GetVector("_DirectionA");
directionB = waves.GetVector("_DirectionB");
directionC = waves.GetVector("_DirectionC");
directionD = waves.GetVector("_DirectionD");
kA = (2 * Mathf.PI) / waveLengthA;
kB = (2 * Mathf.PI) / waveLengthB;
kC = (2 * Mathf.PI) / waveLengthC;
kD = (2 * Mathf.PI) / waveLengthD;
cA = Mathf.Sqrt(Mathf.Abs(Physics.gravity.y)/ kA);
cB = Mathf.Sqrt(Mathf.Abs(Physics.gravity.y) / kB);
cC = Mathf.Sqrt(Mathf.Abs(Physics.gravity.y) / kC);
cD = Mathf.Sqrt(Mathf.Abs(Physics.gravity.y) / kD);
}
private void Update()
{
offset += Time.deltaTime;
}
public float GetWaveHeight(float x,float z)
{
fA = kA*(directionA.x * x + directionA.y * z - cA * offset);
fB = kB * (directionB.x * x + directionB.y * z - cB * offset);
fC = kC * (directionC.x * x + directionC.y * z - cC * offset);
fD = kD * (directionD.x * x + directionD.y * z - cD * offset);
position += new Vector3(x + directionA.x * steepnessA / kA * Mathf.Cos(fA),steepnessA/kA*Mathf.Sin(fA),z+directionA.y*steepnessA/kA*Mathf.Cos(fA));
position += new Vector3(x + directionB.x * steepnessB / kB * Mathf.Cos(fB),steepnessB/kB*Mathf.Sin(fB),z+directionB.y*steepnessB/kB*Mathf.Cos(fB));
position += new Vector3(x + directionC.x * steepnessC / kC * Mathf.Cos(fC),steepnessC/kC*Mathf.Sin(fC),z+directionC.y*steepnessC/kC*Mathf.Cos(fC));
position += new Vector3(x + directionD.x * steepnessD / kD * Mathf.Cos(fD),steepnessC/kD*Mathf.Sin(fD),z+directionD.y*steepnessD/kD*Mathf.Cos(fD));
return position.y;
}
The code above is quite ugly with a lot of repetition, but my plan is to make a constructor at some point to make it easier to read.
I grab all the values used in my shader, to make sure they match even if I change the look of the waves. Then I do the calculations from Catlikecoding and plot in the x- and z-coordinates of my floating object.
As far as I can understand, it should work if I just combine alle the calculated vectors, but obviously I'm missing something.
From what I've seen others do, they often opt to create custom planes with more vertices, that can cover their entire gameworld and avoid the problem, but I'm making a larger world and am worried about performance. (Though I don't know if I should be even.) I really like the fact that my ocean planes are tileable.
Does anyone here know of any solutions or help me solve the issue of worldspace vs objectspace, or how to accurately recreate the time-progression as seen in the shader?
Any help would be much appreciated.
So, for anyone struggling with this, I found the answer.
When combining multiple waves together, the manipulated plane grows in size for every wave added.
In my above question, I had somehow messed up the formulas for calculating the waves. I redid them and got the correct result.
Now, the trick is to simply divide the resultant wave, by the number of waves that you are combining. This will make sure that the actual size of the plane won't change.
You of course need to do this in your waveManager code as well. It's important to keep in mind that you only need the y-coordinate, so you only have to calculate that. For each wave, calculate the y-coordinate and then divide the combined height by the number of waves. This will make the floatation code work as it should!
Hope this helps someone out there who struggled like me.

Jittering when rotating and then translating a matrix in XNA/Monogame

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));

Unity/C# - Set position and rotation manually of object based on another object

I have 3 objects in 3D Space, but they are only 2D representations of the 3D object. Object A (Obj-A) is the parent object, and 2 different objects (Obj-B, Obj-C) that are not attached to Obj-A as a child, but I have to move along as if they were its child. I am moving and rotating Obj-A, and I am setting Obj-B and Obj-C's positions based on Obj-A with an offset sort of like this:
objB.transform.position = objA.transform.position + offset;
objC.transform.position = objA.transform.position - offset;
But of course, when Obj-A rotates, I want Obj-B and Obj-C's position to be affected by its rotation as well.
When I rotate Obj-B and Obj-C based on the same logic as Obj-A, they only rotate on their local axis.
Basically it should look like this:
I am working on Unity, but this part of the project is computed on a C# script independent from Unity. With this, I only have access to the objects' transform positions and the rotation angle, and simply adding Obj-B and Obj-C as children to Obj-A is off the table.
Please help! Thank you!
First you should copy the rotation like
objB.transform.rotation = objA.transform.rotation;
objC.transform.rotation = objA.transform.rotation;
then you should use the offset componentwise with the local coordinate system of objA:
objB.transform.position = objA.transform.position
+ offset.x * objA.transform.right
+ offset.y * objA.transform.up
+ offset.z * objA.transform.forward;
objB.transform.position = objA.transform.position
- offset.x * objA.transform.right
- offset.y * objA.transform.up
- offset.z * objA.transform.forward;

SharpDX how to correct vertex positions after matrix rotation

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.

Obtaining perfect sprite rotation in XNA

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.

Categories