I have a game in landscape left mode. What I am trying to do is get the phone tilt on the x and y axis but a bunch of weird things happen. For example, turning the phone on the z axis affects the calculations of the x and z axis. Quaternions are a hard thing to wrap my head around, any help? The code is my closest solution.
public float rotationX;
public float rotationZ;
public float gyroscopeX, gyroscopeY, gyroscopeZ;
public Quaternion rotated;
private void Start ()
{
Input.gyro.enabled = true;
}
private void FixedUpdate ()
{
rotated = ConvertRotation(Input.gyro.attitude) * GetRotFix() * Quaternion.Euler(Vector3.forward * 90);
rotated *= Quaternion.AngleAxis(-Input.gyro.attitude.z, Vector3.forward);
rotationX = ClampAngle(rotated.x*90, -45, 45);
rotationZ = ClampAngle(rotated.y*90, -45, 45);
transform.rotation = Quaternion.Euler(-rotationZ, 0, rotationX);
}
private static Quaternion ConvertRotation(Quaternion q)
{
return new Quaternion(q.x, q.y, -q.z, -q.w);
}
private Quaternion GetRotFix()
{
if (Screen.orientation == ScreenOrientation.Portrait)
return Quaternion.identity;
if (Screen.orientation == ScreenOrientation.LandscapeLeft || Screen.orientation == ScreenOrientation.Landscape)
return Quaternion.Euler(0, 0, -90);
if (Screen.orientation == ScreenOrientation.LandscapeRight)
return Quaternion.Euler(0, 0, 90);
if (Screen.orientation == ScreenOrientation.PortraitUpsideDown)
return Quaternion.Euler(0, 0, 180);
return Quaternion.identity;
}
I would like the object to rotate only on its x and z axes according to phone's x and y axes independently. However, turning the phone on its z axis will cause the game object to rotate diagonally if the phone is tilting only on one axis.
In the real world, physically, the phone has an orientation, which is a four dimensional vector: 3 dimensions to describe a forward direction (F), and a 4th dimension to describe how much the phone is rotated (R) about that forward direction.
However, due to hardware limitations and poor planning on the part of platform developers, phones only report Euler angles. That is not sufficient for determining an orientation. What this means is that for each possible screen orientation (i.e. ScreenOrientation.LandscapeLeft you observe, there are at least two (indeed infinitely many) Euler angles that are at least perpendicular (i.e. the rotation R is 90º different) and at worst oriented in opposite directions (i.e. F and -F have the same screen orientation).
Use a well-reviewed asset store package instead, or study an open source solution like Parallax instead.
There is no correct answer for how to interpret a 3-dimensional gyroscope measurement as a 4-dimensional orientation, it is a qualitative decision based on your needs. However most games assume that players will be holding their device landscape with the forward direction parallel to the Earth, rotating about that forward (i.e. like driving a car), so that's what most documentation / tutorials online will describe and what you will find is most robust.
Related
Conceptually a quaternion can store the rotation around an axis by some degree.
Now, what is the numerically most robust and least calculation intensive way to rotate the axis?
For example when i have a quaternion, which rotates 90 degrees around the y-axis and i want those 90 degrees to be applied to some other arbitrary axis, described by a normalized vector.
EDIT: Since this also came up i added an answer on how to correctly change the axis a quaternion rotates around with another quaternion.
It is a bit unclear what your actual goal is by doing what you describe.
In order to actually keep the angle but change the axis you would use Quaternion.ToAngleAxis, alter the axis and then pass it back into Quaternion.AngleAxis
like e.g.
Quaternion someRotation;
someRotation.ToAngleAxis(out var angle, out var axis);
var newAxis = Vector3.up;
var newRotation = Quaternion.AngleAxis(angle, new axis);
Or you rotate an existing Quaternion by another one using * like
Quaternion newRotation = someRotation * Quaternion.Euler(90, 0, 0);
which would take the existing rotation and rotate it by 90° around the X axis.
#derHugo's solution, solves the problem i initially asked, but the seconds part of his answer isn't doing what he seemed to be describing. To rotate a quaternions axis of rotation with another quaternion you would need to apply the rotations differently.
E.g. you have a quaternion yQuaternion, which rotates 90° around the y-axis and want to rotate, it's rotation axis by 90° around the x-axis (which would result in a quaternion rotating 90° around the z-axis) you'd have to do the following.
// The quaternion, we want to "rotate"
var yQuaternion = Quaternion.Euler(0f, 90f, 0f);
// The quaternion we want to rotate by.
var xQuaternion = Quaternion.Euler(90f, 0f, 0f);
var result = xQuaternion * yRotation * Quaternion.Inverse(xQuaternion);
What happens here is that we first rotate backwards to our desired axis, then apply the rotation we want to use and then revert the rotation we initally applied.
NOTE: I'm quit sure, that saying "rotate a quaternion" isn't a valid term when talking about this quaternion operations.
In the image above
the red vector is the spider's forward vector
the blue vector is the vector representing the direction between the spider and it's target
In the code below, orientation is a vector that's representing the normal of the terrain, so that the spider gets aligned to it:
Vector3 orientation = GetTerrainNormal();
Quaternion rotationNeeded = Quaternion.FromToRotation(Vector3.up, orientation);
transform.rotation = Quaternion.RotateTowards(
transform.rotation,
rotationNeeded,
RotationSpeed * Time.deltaTime
);
My issue is that I cannot manage to make the spider face its target... When I add any code that would make it rotate towards it, then it's not aligned with the terrain's normals anymore, it says straight...
So basically, how can I make the spider rotate on the Y world axis (I think), while still then being rotated to match the slope?
Full answer
In case it helps someone else, here's the full answer:
Vector3 orientation = GetTerrainNormal();
Vector3 directionToTarget = (target.position - transform.position).Y(0);
float d = Vector3.Dot(directionToTarget, orientation);
directionToTarget -= d * orientation;
if (directionToTarget.sqrMagnitude > 0.00001f) {
directionToTarget.Normalize();
Quaternion rotationNeeded = Quaternion.LookRotation(directionToTarget, orientation);
transform.rotation = Quaternion.RotateTowards(
transform.rotation,
rotationNeeded,
xRotationSpeed * Time.deltaTime
);
}
This answer on the unity forums was extremely helpful: https://forum.unity.com/threads/look-at-object-while-aligned-to-surface.515743/
Try this
Vector3 directionToTarget = target.transform.position - transform.position;
Quaternion rotationNeeded = Quaternion.LookRotation(directionToTarget, orientation);
First of all, I'm not sure why you need a code to orient the spider manually to the terrain. You can make the spider a Rigidbody and the Unity engine will take care of it for you.
Regardless, you want to rotate the spider around the local Y-Axis (this will keep the current orientation).
You can do this using transform.LookAt() (referring to the blue vector in the picture) (documented here) and passing the up vector as the 2nd argument.
I am trying to rotate an object on its z axis, given a point I calculated at an angle with Atan2 function. Then I create a Quaternion to enter it in the rotation of the object. However, it does not rotate in the supposedly correct direction. If I diagram the angles given by the Atan2 functions, I visualize a clockwise system of angles, but if I diagram the angles that should be so that my object is rendered in the correct direction, I visualize an anti-clockwise system. The solutions creating a dictionary with the values received by the Atan2 function as keys and their values are the angles with which the object will rotate the correct direction. But I still don't understand what is happening. I hope someone can help me understand it because there is no worse solution than the one that solves but without knowing what is happening.
public class ArrowMovement : MonoBehaviour
{
public Vector2Variable currentPlayerDirection;
public Vector3Variable currentPlayerPosition;
public InputAxis inputAxis;
private float anglePassed;
private Dictionary<float, float> realAngles = new Dictionary<float, float>();
void Awake()
{
realAngles.Add(-135, -135);
realAngles.Add(-90, 180);
realAngles.Add(-45, 135);
realAngles.Add(0, 90);
realAngles.Add(45, 45);
realAngles.Add(90, 0);
realAngles.Add(135, -45);
realAngles.Add(180, -90);
}
void Update()
{
Vector3 offsetVector = new Vector3(0.2f, 0.05f, 0);
transform.position = currentPlayerPosition.Value;
// Rotation
float angle = Mathf.Atan2(inputAxis.horizontal, inputAxis.verticall) * Mathf.Rad2Deg;
Quaternion rotation = Quaternion.Euler(0, 0, realAngles[angle]);
transform.rotation = rotation;
}
}
First of all, you should know what inputAxis is.
I can guess from your diagram that you expect that for inputAxis.verticall == 0 the angle must be 0. Let's look at Mathf.Athan2(y,x) - it is equivalent to arctan(y/x), so result will be zero only when y is zero. But, in your code Mathf.Atan2(inputAxis.horizontal, inputAxis.verticall) will give 0 when inputAxis.horizontal == 0 - it is vertical direction (your first diagram).
Second issue - wrong rotation direction. This can happen when one of the input axes is inverted. Probably, inputAxis is measured in a different coordinate system relative to your object, i.e. Z of your object is -Z of your inputAxis. Anyway, inputAxis.verticall can be inverted
float angle = Mathf.Atan2(inputAxis.horizontal, -inputAxis.verticall) * Mathf.Rad2Deg;
Also, you should avoid solving a simple geometric problem by "creating a dictionary" or anything like that. Even if you don't know how inputAxis works and result is wrong - make two step to fix it: 1. fix rotation direction - just invert the angle Quaternion.Euler(0, 0, -angle); 2. fix the zero position - add some static rotation. It is 90 deg in your case Quaternion.Euler(0, 0, 90 - angle). And it is the second way to solve the problem.
Creating a dictionary is not the best way because it does not take into account the angles between the ones you set so to convert from one diagram to the other I would create a function that looks like:
float ChangeAngleDiagram(float angle) {
return -(angle + 90f)+180;
}
I'm trying to constrain an object's rotation, so that it behaves like a joystick (meaning it can only rotate up to some maximum angle from center).
I tried to constrain rotation on each individual axis, but they behaved really weirdly when rotating (the angle values didn't just grow linearly). The input I'm using to supply this rotation is the rotation of physical controller. How can I do this?
This is how it works now and how I want it to work:
Images are 2D but it applies to all axes.
It sounds to me like there are two parts to the problem:
Determine whether supplied rotation exceeds maximum allowable rotation of object
If the supplied rotation is too large, reducing that rotation so it fits within the rotational constraints, then applying it
In my code examples, I'll be assuming that the initial rotation of the virtual joystick is zero across the board (Quaternion.identity), and that your supplied rotation is called newRotation.
For the first part, Quaternion.Angle() comes to mind. This gives the angle between two given rotations, and can be used like so:
if (Quaternion.Angle(Quaternion.identity, newRotation) < 30){
// Angle from initial to new rotation is under 30 degrees
}
For the second part, you'll need some way of reducing the supplied rotation so it is within the allowable angle from the initial rotation. For that, Quaternion.Slerp() is useful. This allows you to interpolate between two rotations, returning a Quaternion that is a combination of the two supplied. For example, this gives back half of newRotation:
Quaternion.Slerp(Quaternion.identity, newRotation, 0.5f);
Putting these two methods together, you can write a clamping method which ensures that the supplied rotation never exceeds a certain angle from the initial rotation. Here's an example usage:
// Maximum angle joystick can tilt at
public float tiltAngle;
// If this isn't your initial rotation, set it in Awake() or Start()
Quaternion initialRotation = Quaternion.identity;
void Update(){
Quaternion newRotation = MethodToGetSuppliedRotation();
Quaternion clampedRotation = ClampRotation(initialRotation, newRotation, tiltAngle);
transform.localRotation = clampedRotation;
}
// Clamps "b" such that it never exceeds "maxAngle" degrees from "a"
Quaternion ClampRotation(Quaternion a, Quaternion b, float maxAngle){
float newAngle = Quaternion.Angle(a, b);
if (newAngle <= maxAngle){
// Rotation within allowable constraint
return b;
}
else{
// This is the proportion of the new rotation that is within the constraint
float angleRatio = maxAngle / newAngle;
return Quaternion.Slerp(a, b, angleRatio);
}
}
Hope this helps! Let me know if you have any questions.
I am using the following code to handle rotating my player model to the position of my mouse.
void Update() {
// Generate a plane that intersects the transform's position with an upwards normal.
Plane playerPlane = new Plane(Vector3.up, transform.position);
// Generate a ray from the cursor position
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
// Determine the point where the cursor ray intersects the plane.
// This will be the point that the object must look towards to be looking at the mouse.
// Raycasting to a Plane object only gives us a distance, so we'll have to take the distance,
// then find the point along that ray that meets that distance. This will be the point
// to look at.
float hitdist = 0f;
// If the ray is parallel to the plane, Raycast will return false.
if (playerPlane.Raycast(ray, out hitdist)) {
// Get the point along the ray that hits the calculated distance.
var targetPoint = ray.GetPoint(hitdist);
// Determine the target rotation. This is the rotation if the transform looks at the target point.
Quaternion targetRotation = Quaternion.LookRotation(targetPoint - transform.position);
// Smoothly rotate towards the target point.
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, speed * Time.deltaTime); // WITH SPEED
//transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, 1); // WITHOUT SPEED!!!
}
I would like to be able to determine if the rotation is clockwise or counter-clockwise for animation purposes. What would be the best way of handling this? I'm fairly unfamiliar with quaternions so I'm not really sure how to approach this.
Angles between quaternions are unsigned. You will always get the shortest distance, and there's no way of defining "counter-clockwise" or "clockwise" unless you actively specify an axis (a point of view).
What you CAN do, however, is to take the axis that you're interested in (I assume it's the normal to your base plane.. perhaps the vertical of your world?) and take the flat 2D components of your quaternions, map them there and compute a simple 2D angle between those.
Quaternion A; //first Quaternion - this is your desired rotation
Quaternion B; //second Quaternion - this is your current rotation
// define an axis, usually just up
Vector3 axis = new Vector3(0.0f, 1.0f, 0.0f);
// mock rotate the axis with each quaternion
Vector3 vecA = A * axis;
Vector3 vecB = B * axis;
// now we need to compute the actual 2D rotation projections on the base plane
float angleA = Mathf.Atan2(vecA.x, vecA.z) * Mathf.Rad2Deg;
float angleB = Mathf.Atan2(vecB.x, vecB.z) * Mathf.Rad2Deg;
// get the signed difference in these angles
var angleDiff = Mathf.DeltaAngle( angleA, angleB );
This should be it. I never had to do it myself and the code above is not tested. Similar to: http://answers.unity3d.com/questions/26783/how-to-get-the-signed-angle-between-two-quaternion.html
This should work even if A or B are not Quaternions, but one of them is an euler-angle rotation.
Two dimensional quaternions (complex numbers) have a signed angle. But, the more correct way to think about complex numbers is with an unsigned angle which is relative to either the XY oriented plane or the YX oriented plane. I.E. a combination of an unsigned angle an an oriented plane of rotation.
In 2D there are only two oriented planes of rotation so the idea of a "signed angle" is really just a trick to get both the unsigned angle and the oriented plane of rotation packed into a single number.
For a quaternion the "signed angle" trick cannot be used because in 3D you have an infinite number of oriented planes you can rotate in, so a single signed angle cannot encode all the rotation information like it can in the 2D case.
The only way for a signed angle to make sense in 3D is with reference to a particular oriented plane, such as the XY oriented plane.
-- UPDATE --
This is pretty easy to solve as a method on a quaternion class. If all you want to know is "is this counter clockwise", then since we know the rotation angle is from 0 to 180, a positive dot product between the quat's axis of rotation and the surface normal should indicate that we're rotating counter clockwise from the perspective of that surface. And a negative dot product indicates the opposite. Ignoring the zero case, this should do the trick with much less work:
public bool IsCounterClockwise( in Vector3 normal ) => I*normal.X + J*normal.Y + K*normal.Z >= 0;