I have got a very large problem with rotation in Unity. What I want:
I have two 3D objects. Just one is for player manipulating, second object Transform.rotation and Transform.position is dependent on object number one with scale of 1/10. It means if I will move first object from (0,0,0) to (10,30,90) then obj.2 will move from (0,0,0) to (1,3,9). It's simple. But I have got LARGE problem with rotation.
I can't make rotation on normal transform because it's based on "local position".
Below I present my problem with simplest 2D object situation:
As you can see when I rotate red object +90 degrees the second object rotate +9 degrees and the axes become different in relation to the world. After more transformations in 3D world it make a large mess. For example after some transformations if I will want to rotate 3D object from me (like using accelerator on motocycle) on first make second object rotating from left to right (because it's based on object axis).
Of course using Transform.Rotate instead of Transform.localRotate (or Transform.EulerAngles instead of Transform.localEulerAngles) is not a solutions because it's means only if objects are childrens (it this this case are not).
WHAT I FOUND:
Using Transform.Rotate(Xdegree,Ydegree,Zdegree, Space.World) is solution for rotating second object !
What I need:
Xdegree, Ydegree and Zdegree from first (manipulated by player) object.
Transform.EulerAngles and Transform.Rotation DOESN'T work because it's returns "local objects" rotations.
So... I know that if 3D obj.2 rotation is (0;30;0) and i use obj2.Rotate(45,0,0) then the obj.2 rotation will be (~37.76;~39.23;~26.56) and it's okay. But I dont know how to convert the other way (from "local" rotation XYZ to degrees that I can use on Transform.Rotate() (of course I will divided this values (xyz) by 10 at the end because I have got 1/10 moving scale))
If you need one GameObject to have 1/10 of the rotation and position of another, you could use something like:
//the player-controlled cube
public Transform t1;
//the 1/10 cube
public Transform t2;
void Update(){
//set the position of t2 to 1/10 of the position of t1
t2.position = 0.1f * t1.position;
//get the axis and angle of t1's rotation
t1.rotation.ToAngleAxis(out float angle, out Vector3 axis);
//t2 should be rotated in the same direction (axis), but with 1/10th of the angle
t2.rotation = Quaternion.AngleAxis(angle * 0.1f, axis);
}
Edit: To allow resetting delta rotation and changing targets, you could do something like this. Note: this glitches when it wraps more than a full circle, I'm not an expert on Quaternions so you'd have to figure it out yourself.
//the player-controlled cube
public Transform t1;
//the 1/10 cube
public Transform t2;
private Vector3 t1originalPosition;
private Quaternion t1originalRotation;
private Vector3 t2originalPosition;
private Quaternion t2originalRotation;
void Start()
{
ResetTarget(t1);
}
void Update()
{
if (t1 != null)
{
//set the position of t2 to 1/10 of the position of t1
t2.position = t2originalPosition + 0.1f * (t1.position - t1originalPosition);
Quaternion t1Rotation = t1.rotation * Quaternion.Inverse(t1originalRotation);
//get the axis and angle of t1's rotation
t1Rotation.ToAngleAxis(out float angle, out Vector3 axis);
//t2 should be rotated in the same direction (axis), but with 1/10th of the angle
t2.rotation = Quaternion.AngleAxis(angle * 0.1f, axis) * t2originalRotation;
}
}
public void ResetTarget(Transform target = null)
{
t2originalPosition = t2.position;
t2originalRotation = t2.rotation;
t1 = target;
t1originalPosition = t1.position;
t1originalRotation = t1.rotation;
}
Use quaternions instead of the euler angles (xyz rotation angles). And simply give the global rotation value (quaternion) of one object to the other.
To add together quaternions, you just multiply them together.
Related
I would like to recreate one on one the rotation of the real life controller joystick (i.e. 360 controller) into a 3D joystick mesh (that resembles the 360 controller one).
I thought about doing it by rotating the joystick in the X axis according to the magnitude of the input (mapping it to a min and max rotation in the X axis). And then figure the angle of the input and apply it to the Y axis of the 3D joystick.
This is the code I have, the joystick tilts properly in the X axis but the rotation in the Y axis doesn't work:
public void SetStickRotation(Vector2 stickInput)
{
float magnitude = stickInput.magnitude;
// This function converts the magnitude to a range between the min and max rotation I want to apply to the 3D stick in the X axis
float rotationX = Utils.ConvertRange(0.0f, 1.0f, m_StickRotationMinX, m_StickRotationMaxX, magnitude);
float angle = Mathf.Atan2(stickInput.x, stickInput.y);
// I try to apply both rotations to the 3D model
m_Stick.localEulerAngles = new Vector3(rotationX, angle, 0.0f);
}
I am not sure why is not working or even if I am doing it the right way (i.e. perhaps there is a more optimal way to achieve it).
Many thanks for your input.
I would recommend rotating it by an amount determined by the magnitude around a single axis determined by the direction. This will avoid the joystick spinning around, which would be especially noticeable in cases of asymmetric joysticks such as pilots joysticks:
Explanation in comments:
public void SetStickRotation(Vector2 stickInput)
{
/////////////////////////////////////////
// CONSTANTS (consider making a field) //
/////////////////////////////////////////
float maxRotation = 35f; // can rotate 35 degrees from neutral position (up)
///////////
// LOGIC //
///////////
// Convert input to x/z plane
Vector3 stickInput3 = new Vector3(stickInput.x, 0f, stickInput.y);
// determine axis of rotation to produce that direction
Vector3 axisOfRotation = Vector3.Cross(Vector3.up, stickInput3);
// determine angle of rotation
float angleOfRotation = maxRotation * Mathf.Min(1f, stickInput.magnitude);
// apply that rotation to the joystick as a local rotation
transform.localRotation = Quaternion.AngleAxis(angleOfRotation, axisOfRotation);
}
This will work for joysticks where:
the direction from its axle to its end is the local up direction,
it should have zero (identity) rotation on neutral input, and
stickInput with y=0 should rotate the knob around the stick's forward/back axis, and stickInput with x=0 should rotate the knob around the stick's left/right axis.
Figure out the problem, atan2 returns the angle in radiants, however the code assumes it is euler degrees, as soon as I did the conversion it worked well.
I put the code here if anyone is interested (not the change in the atan2 function):
public void SetStickRotation(Vector2 stickInput)
{
float magnitude = stickInput.magnitude;
// This function converts the magnitude to a range between the min and max rotation I want to apply to the 3D stick in the X axis
float rotationX = Utils.ConvertRange(0.0f, 1.0f, m_StickRotationMinX, m_StickRotationMaxX, magnitude);
float angle = Mathf.Atan2(direction.x, direction.y) * Mathf.Rad2Deg;
// Apply both rotations to the 3D model
m_Stick.localEulerAngles = new Vector3(rotationX, angle, 0.0f);
}
I am trying to determine the angle where the camera forward vector intersect with an object vector.
Sorry, not straight forward to explain with my knowledge, please find attached a diagram: The camera may not be looking directly at the object (OBJ) and I'd like to know the angle ( ? in the diagram) where the camera's forward vector (V1 in red) intersects with the vector of the object (V2 in red) (if it does), e.g. point A, or B, or C, etc depending on the x-rotation of the camera.
I tried calculating a normalized vector for the red lines, v1 and v2.
Then calculate the angle between two vectors https://onlinemschool.com/math/library/vector/angl/
But the results don't match the expected values when testing.
//v1
Vector3 hypoth = Camera.main.transform.forward.normalized;
//v2
Vector3 adjacent = (new Vector3(obj.transform.position.x, obj.transform.position.y, Camera.main.transform.position.z)
-obj.transform.position).normalized;
float dotProd = Vector3.Dot(adjacent, hypoth);
float cosOfAngle = dotProd / (Vector3.Magnitude(adjacent) * Vector3.Magnitude(hypoth));
double radAngle = Math.Acos(cosOfAngle);
float angle = (float)((180 / Math.PI) * radAngle);
Finding the angle between v1 and v2 gives you this angle, which doesn't match what you mark in your diagram:
Instead, solve for the angle between v1 and the plane normal to v2:
We can do this in unity by projecting v1 to the plane normal to v2 using Vector3.ProjectOnPlane, and then finding the angle between that projection and v1 using Vector3.Angle:
Vector3 projection = Vector3.ProjectOnPlane(hypoth, adjacent);
float angle = Vector3.Angle(projection, hypoth);
I've a similar situation where I wanted to set the collidars of the terrain units on the same height of the player Jet and at the same time it must be on the line of sight of the camera, otherwise when u shoot the terrain units , the bullets will appear like moving through enemy units on the ground , this only works when you work with prospective camera, on orthongal , u may dont need to do this at all, its just set the object on the same height as the camera and everything will be aligned .
Here is my code
void SetColliderLocation()
{
// Object on the ground
A = TerrainUnit.transform.position;
// Camera location
B = cam.transform.position;
// Enemy jet height
height = mainPlayerTransform.position.y;
// Unit Vector normalized between A and B
AB_Normalized = (A - B).normalized;
// The unit vector required to move the collider to maintain its hieght and line of sight with the camera
unitVector = (height - A.y) / AB_Normalized.y;
// Setting the location of the collidar .
collidarGameObject.transform.position = (AB_Normalized * unitVector) + A;
}
I hope its some how similar of what you are looking for.
Edit:
If you applied this script and instead of collider you put a box , you will see the box location will be always between the camera on the sky and object on the ground however the camera or the object on the ground is moving.
I need to display the rotation in Euler angles of an object's certain axis.
I am aware that retrieving the rotation of an object in Euler angles gives inconsistent results, some of which can be solved by simply using modulo 360 on the result. however one permutation that unity sometimes does when assigning a vector with the value of "transform.localRotation.eulerAngles" is instead of retrieving the Vector3 "V", it retrieves "(180, 180, 180) - V".
to my understanding, "(180, 180, 180) - V" does not result in the same real world rotation as V, unlike "(180, 180, 180) + V" which does leave the actual rotation unaffected.
what is the explanation for the phenomenon, and what is the best way of normalizing an Euler angles rotation vector assuming I know the desired and feasible value of one of its axes? (for example, to normalize it such that all of it's values are mod 360 and it's Z axis equals 0 assuming it does have a representation in which Z = 0)
I don't know about the first part of the question (it is different enough to be its own question imo) but I can answer your second one.
So, you have these inputs :
Quaternion desiredRotation;
float knownZ;
And you're trying to find Vector3 eulers where eulers.z is approximately knownZ and Quaternion.Euler(eulers) == desiredRotation.
Here's the procedure I would use:
First, determine the up direction rotated by desiredRotation and the up and right direction rotated by a roll of knownZ:
Vector3 upDirEnd = desiredRotation * Vector3.up;
Quaternion rollRotation = Quaternion.Euler(0,0,knownZ);
Vector3 upDirAfterRoll = rollRotation * Vector3.up;
Vector3 rightDirAfterRoll = rollRotation * Vector3.right;
We know the local up direction after desiredRotation is applied and that the only thing that can adjust the up direction after the roll knownZ is applied is the rotation done by the euler pitch component. So, if we can calculate the angle from upDirAfterRoll to upDirEnd as measured around the rightDirAfterRoll axis...
float determinedX = Vector3.SignedAngle(upDirAfterRoll, upDirEnd, rightDirAfterRoll);
// Normalizing determinedX
determinedX = (determinedX + 360f) % 360f;
...we can determine the x component of eulers!
Then, we do the same with the yaw component of eulers to make the new forward direction line up with the end forward direction:
Vector3 forwardDirEnd = desiredRotation * Vector3.forward;
Quaternion rollAndPitchRotation = Quaternion.Euler(determinedX, 0, knownZ);
Vector3 forwardDirAfterRollAndPitch = rollAndPitchRotation * Vector3.forward;
Vector3 upDirAfterRollAndPitch = upDirEnd; // unnecessary but here for clarity
float determinedY = Vector3.SignedAngle(forwardDirAfterRollAndPitch, forwardDirEnd, upDirAfterRollAndPitch );
// Normalizing determinedY
determinedY = (determinedY + 360f) % 360f;
Vector3 eulers = new Vector3(determinedX, determinedY, knownZ);
To ensure that the given quaternion can be made with the given component, you could check if the axes given to SignedAngle actually can rotate the input vector to the target vector, or you can just compare the calculated eulers and the given quaternion:
Quaternion fromEuler = Quaternion.Euler(eulerAngles);
if (fromEuler==desiredRotation)
{
// use eulerAngles here
}
else
{
// component and quaternion incompatible
}
Hopefully that helps.
I'm not quite sure I understand your question correctly, but the euler angles just represent the angles of 3 rotations applied around the 3 axis in a specific order, right? So why would you normalize it by adding 180 everywhere? You should bring each angle individually into the range 0-360 by modulo-ing them.
Your question seems to imply that you can obtain any orientation by only rotating around two axis instead of three... is that what you are trying to achieve?
Using quaternions could possibly help you, in fact an orientation can be defined with just 4 scalar values: an axis and an angle
I have some problems with a rotating marble.
I've tried it with Matrix.CreateFromYawPitchRoll and Matrix.CreateRotation but there were some problems, I think it's due to the Gimbal lock effect.
So, I've tried using quaternions instead, but nothing changed.
When moving on only an axis it works fine, but when the rotation occurs on two different axes the marble still rotates on wrong axes.
Here's my code:
// declarations
Vector3 Position = Vector3.Zero;
Vector3 Rotation = Vector3.Zero;
Quaternion qRotation = Quaternion.Identity;
AbsoluteBoneTransforms = new Matrix[Model.Bones.Count];
Model.CopyAbsoluteBoneTransformsTo(AbsoluteBoneTransforms);
In the Update method:
Position += speed;
Rotation = speed * MathHelper.ToRadians(-1.5f);
Quaternion rot = Quaternion.CreateFromAxisAngle(Vector3.Right, Rotation.Z) *
Quaternion.CreateFromAxisAngle(Vector3.Backward, Rotation.X);
qRotation *= rot;
And in the Draw method:
effect.World = AbsoluteBoneTransforms[mesh.ParentBone.Index] *
Matrix.CreateFromQuaternion(qRotation) * Matrix.CreateTranslation(Position);
What's wrong? Is it wrong to use Quaternion.CreateFromAxisAngle on multiple axes?
EDIT
I've tried calculating directly the axis of rotation of my marble, instead of using combination of multiple axes:
angle += speed.Length() * angularVelocity;
qRotation = Quaternion.CreateFromAxisAngle(Vector3.Cross(speed, Vector3.Up), angle);
qRotation.Normalize();
angle is a float that keeps track of the current movement.
This solution doesn't seem to create Gimbal lock, but marble rotations aren't correct, it seems that the rotating speed is not constant, but became faster and slower over time, I can't understand why.
If I "concatenate" the quaternions I get every frame using
qRotation *= Quaternion.CreateFromAxisAngle(Vector3.Cross(speed, Vector3.Up), angle)
the Gimbal lock effect is still visible.
Here's how I've tackled that:
I'm assuming speed is a vector representing the direction the ball is rolling and whose magnitude represents the rate it is traveling in that direction.
Vector3 axis = Vector3.Cross(speed, Vector3.Up);
float angle = speed.Length();//factor by delta time if neccesary
Quaternion rotationThisFrame = Quaternion.CreateFromAxisAngle(axis, angle * (1/radiusOfBall));
then you can concatenate that to your qRotation. Also, you may need to normalize your quaternion after concatenation.
Update: The correct answer to this question/thread was reversing the order that quaternions concatenate in. With respect to XNA, matrices combine left to right, quaternions combine right to left.
I have been working with rotating Texture2D sprites. I have been using:
float circle = MathHelper.Pi * 2;
RotationAngle = RotationAngle % circle;
and
ScreenManager.SpriteBatch.Draw(car, screenpos, null, Color.White, RotationAngle, car_origin, 1.0f, SpriteEffects.None, 0f);
for the most part to handle the rotation of my test texture "car". It seems like the rotation angle of Pi*2 is a value between 0 and -6.283185 or 0 and 6.283185 depending on the direction. Now what I would like to do is rotate a texture in a certain direction (say the texture is an arrow) towards a location (a vector2 such as the current mouse position). I am not quite sure how to go about how I'd need to modify this rotation angle based on a vector2 position.
You don't need to wrap an angle when passing it to SpriteBatch.Draw. But if you do want to wrap an angle for some reason, it's best to use MathHelper.WrapAngle (MSDN).
Now say you have a Vector2 that represents a direction and a distance (as you might have, for example, if you did mousePos - carPos, for the direction and distance from the car to the cursor). And you want to take that direction and convert it to an angle. Use an extension method like this one:
public static float Angle(this Vector2 v)
{
return (float)Math.Atan2(v.Y, v.X);
}
So, to get your angle you'd do something like: (mousePos - carPos).Angle().
See the documentation for Atan2 for more details.