Retrieve object rotation from face normal - c#

I would like to rotate an object to match its ground. So i cast 3 Rays (at the corners) to calculate the normal of the plane below.
Now i need to rotate the object accordingly but keep the y rotation (so in which direction it "faces") so just setting transform.up = normal does not work.
I thought i could just use the dot product between the transform directions to rotate it (so xRotation = Vector3.Dot(normal, transform.forward) for x and zRotation = Vector3.Dot(normal, transform.right) for z) this should be the angles between the normal vector and the right/forward vector. But as the result my object just faces the sky that way to the idea is completely wrong.
Do you know how i should proceed ?

Here is the solution to your problem. Although there are different methods for doing this, I personally find it best to use the Cross axis. In fact, you need Vector3.Cross instead of Vector3.Dot. This code works in such a way that by multiplying the transform.right of the player on the Ground normal vector, Since this axis calculates the perpendicular direction, you can expect it to give a forward corresponding to the ground.
public LayerMask groundLayer; // The Layer Ground
public Vector3 offset = Vector3.up;
private void Update()
{
if (Physics.Raycast(transform.position, Vector3.down, out var groundHit, 2f, groundLayer.value))
{
var cross = Vector3.Cross(transform.right, groundHit.normal);
var _lookRotation = Quaternion.LookRotation(cross, groundHit.normal);
transform.position = groundHit.point + offset; // the offset is OPTIONAL, you can adjust it manuel or remove
transform.rotation = _lookRotation;
}
}
Result:
You can see the result below. Consider that you can delete the offset code and make it compatible with the ground with mechanisms such as CharacterController or Rigidbody.

Related

When rotating an object in Unity, the script doesn't rotate the object on the Z-axis, only the X and Y?

I have an rectangular cube that needs to be placed between two other objects, and it properly rotates so that each end faces the two objects. Since this happens on a spherical world, the issue lies when I try to then align it to be properly facing "up". I didn't realize this before, but this code (which works on the other objects as they only need to orient once) only uses the X and Y axes. I can manually orient them properly by modifying the Z-axis in the Editor, so I know it's possible, but since they must be created procedurally that obviously isn't feasible. Changing from transform.up to down or right changes absolutely nothing.
In the following code, parent is a one of the objects it has to orient towards (which it does fine), and target is the center of the sphere.
In theory, the alignment to the two side objects should only need to use the X and Y axes, and the alignment to the center of the sphere then only needs to use the Z axis as represented in the final line of code, yet in practice this doesn't work as both alignments both want to use only the X and Y axes and ignore the Z axis.
Vector3 parentDirection = parent.transform.position - transform.position;
Vector3 targetDirection = target.position - transform.position;
Vector3 DirectionToParent = Vector3.RotateTowards(transform.forward, parentDirection, 100f, 0.0f);
Vector3 CenterDiretion = Vector3.RotateTowards(transform.forward, targetDirection, 100f, 0.0f);
Quaternion ParentRotation = Quaternion.LookRotation(DirectionToParent );
Quaternion CenterRotation = Quaternion.LookRotation(CenterDiretion );
transform.rotation = Quaternion.Euler(DirectionToParent.eulerAngles.x, DirectionToParent.eulerAngles.y, newRotation.eulerAngles.z);
The code shown in the question doesn't work because it doesn't take into account the direction to/away the center of the planet when calculating transform.rotation.
Instead, I would recommend just using Quaternion.LookRotation to assign the rotation that would produce the forward you have with parentDirection, and the up closest to the up direction of the planet.
Vector3 upDirection = transform.position - target.position;
transform.rotation = Quaternion.LookRotation(parentDirection, upDirection);
And if you need to change the rotation based on some max velocity, just assign the result of Quaternion.LookRotation to a variable and Quaternion.RotateTowards from transform.rotation and assign the result to transform.rotation as necessary:
private float turnVelocity = 100f;
// ...
Vector3 upDirection = transform.position - target.position;
Quaternion goalRot = Quaternion.LookRotation(parentDirection, upDirection);
transform.rotation = Quaternion.RotateTowards(transform.rotation, goalRot,
turnVelocity * Time.deltaTime);
}

Unity3D make object follow another but to move further

So, here is what i have:
SteamVR's hand gameobject
3D sphere.
what i want:
The sphere to move to same direction/position as the hand does, but it to move further with a multiplier. E.g. i move the VR controller and the hand moves 1 unit. I want that the sphere moves to the same direction in a same amount of time but e.g. 2 units. how do i do this?
i tried simple
sphere.transform.position = ControllerToFollow.position +2f;
but then the sphere is always offset.
position is a Vector3, which is essentially 3 floats - you can't plus a Vector3 with a float unless you overload the + operator. Otherwise what you can do is the following:
Vector3 followPos = new Vector3(ControllerToFollow.position.x + 2f,
ControllerToFollow.position.y + 2f,
ControllerToFollow.position.z + 2f);
sphere.transform.position = followPos;
If you only want it to follow on one axis, then you can do the following:
Vector3 followPos = new Vector3(ControllerToFollow.position.x + 2f, // Follow on x Axis
ControllerToFollow.position.y, // Y axis is the same
ControllerToFollow.position.z); // X Axis is the same
sphere.transform.position = followPos;
Edit: I think I understand your problem better now. Here's a better version.
if (Vector3.Distance(sphere.transform.position, ControllerToFollow.position) >= 2f)
{
// Code that makes the sphere follow the controlling
}
Just track the movement Delta of the hand and multiply it by a certain multiplier.
At the beginning of the manipulation store
private Vector3 lastControllerPosition;
...
lastControllerPosition = ControllerToFollow.position;
then in every frame compare
var delta = ControllerToFollow.position - lastHandPosition;
// Don't forget to update lastControllerPosition for the next frame
lastControllerPosition = ControllerToFollow.position;
Now in delta you have a movement of the controller since the last frame. So you can assign it to the sphere with a multiplier using Transform.Translate
sphere.transform.Translate(delta * multiplier, Space.World);
or simply using
sphere.transform.position += delta * multiplier;

how to make the transform.up of a gameobject be colliear with a vector, without changing it's Y rotation?

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.

How to Record Direction of NavMesh AI agents

I am wondering on a way on how I can grab the direction a Navmesh Agent is walking, with these values I planned on referencing them to a Blendtree to where certain animations would play depending on the AI direction, Using a 2D Free form Cartesian plane I planned on using those referenced direction values to play certain animations, with the X and Z directions both starting off at zero. so to walk forward the Z value would be 1, while the X value would be 0
Currently I have already tried using rigid body velocity and transform.forward (X and Z), while I received no values using velocity the transform.forward did give increasing values however it did not seem like the values I was looking for (these values need to be in a range from 0-1), as well as never had a starting position(Idle) of 0,0
void animationUpdate() {
anim.SetFloat("Enemy Z", /* Unsure what to do*/);
anim.SetFloat("Enemy X", /* Unsure what to do*/);
if (/*transform.forward.x != 0 && transform.forward.z != 0*/) {
anim.SetBool("isWalking", true);
} else {
anim.SetBool("isWalking", false);
}
}
My Cartesian Plane is the following
Any Help would be appreciated,
thank you for reading
You can use desiredVelocity to find what direction it's moving, and then Vector3.Project to decompose that into how it's moving forward/right.
Vector3 normalizedMovement = nav.desiredVelocity.normalized;
Vector3 forwardVector = Vector3.Project(normalizedMovement, transform.forward);
Vector3 rightVector = Vector3.Project (normalizedMovement, transform.right);
Get the magnitude (which, because we projected the normalized movement, will be a float between -1 and 1) and use Vector3.Dot to determine the sign of the magnitude in each component:
// Dot(direction1, direction2) = 1 if they are in the same direction, -1 if they are opposite
float forwardVelocity = forwardVector.magnitude * Vector3.Dot(forwardVector, transform.forward);
float rightVelocity = rightVector.magnitude * Vector3.Dot(rightVector, transform.right);
Now, you can assign them to your animation, converting the range from [-1,1] to [0,1] using Mathf.InverseLerp:
anim.SetFloat("Enemy Z", Mathf.InverseLerp(-1f, 1f, forwardVelocity));
anim.SetFloat("Enemy X", Mathf.InverseLerp(-1f, 1f, rightVelocity));

Create a Vector3 position by angle differences

I have a player position, a pointer indicating the players view direction, a distance and a horizontal and vertical angle. I want to calculate a target position:
that is distance away from the players position
that, from the players view direction, is horizontal angle to
the right and vertical angle up
It's about positioning a Hololens-Application UI in a sphere around the player. The UI should i.e. be 40 degrees to the leftand 20 degrees up from the players view direction.
Edit: Added image to clarify. Given is the Player Pos (pX|pY|pZ), the radius (= length of the black bold line) and both angles in degree.
I'm looking for how to calculate the UI Center position (x?|y?|z?).
You can use Quaternion.Euler to create a rotation based on angles in world space and then get the desired result by multiplying it with a known position.
So by using your example you could find the position like this:
float radius, x_rot, y_rot;
Vector3 forwardDirection, playerPos;
Vector3 forwardPosition = playerPos + (forwardDirection * radius);
Vector3 targetPosition = Quaternion.Euler(x_rot, y_rot, 0) * forwardPosition;
Try check out the docs on Quaternion and Quaternion.AngleAxis for more handy rotation stuff.
Answer by a mathematician:
To calculate the spherical position with the given information (distance between objects, x angle, y angle) you use trigonometry:
float x = distance * Mathf.Cos(yAngle) * Mathf.Sin(xAngle);
float z = distance * Mathf.Cos(yAngle) * Mathf.Cos(xAngle);
float y = distance * Mathf.Sin(yAngle);
ui.transform.position = player.transform.position + new Vector3(x,y,z);
// Set UI in front of player with the same orientation as the player
ui.transform.position = player.transform.position + player.transform.forward * desiredDistance;
ui.transform.rotation = player.transform.rotation;
// turn it to the left on the players up vector around the the player
ui.transform.RotateAround(player.transform.position, player.transform.up, -40);
// Turn it up on the UI's right vector around the player
ui.transform.RotateAround(player.transform.position, ui.transform.right, 20);
assuming you also want the UI to face the player, otherwise you have to set another rotation after this.
No need to calculate it yourself, the Unity API already does it for you (
see Rotate around)
If i am understanding you correctly you want to create a UI that hovers above a point. I recently did a similar thing in my game. and this is how i did it.
if (Input.GetMouseButtonDown(0)) // use the ray cast to get a vector3 of the location your ui
// you could also do this manualy of have the computer do it the main thing is to
// get the location in the world where you want your ui to be and the
// WorldTOScreenPoint() will do the rest
{
RaycastHit hit;
Vector3 pos;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit))
{
pos = hit.point;
pos.y += yOffset; // use the offset if you want to have it hover above the point
ui.transform.position = cam.WorldToScreenPoint(pos); // use your main cammera here
// then either make your ui vissible or instanciati it here and make sure if you instanciate it
// that you make it a child of your cnavas
}
}
I hope this solves you problem. If i am not understanding what you are trying to do let me know and i will try to help.
Note: if you want to make the ui look farther away when you move away from the point scale the ui down as you move farther away, and scale it up when you get closer.
The diagram in the question is somewhat confusing:
The axes are in the orientation of a right-handed coordinate system, but Unity uses a left-handed coordinate system.
In terms of Euler angles, the part of the image labeled "x Angle" is actually the Y angle (rotation around Y axis), and the part of the image labeled "y Angle" is actually the X angle (around X axis).
The two angles listed use a different sign convention. The Y angle (labeled "x Angle") is following the right-hand rule, while the other angle is not.
Jonas Zimmer has a great answer that follows the conventions in the image, but I'll try to do something a bit less confusing and follows more standard math conventions.
Here is some code for Unity written in C#, in YX rotation order, treating zero angle as forward (+Z), and follows Unity's conventions of a left-handed, Y-is-up, Z-is-forward coordinate system. Increasing Y angle rotates to the right, and increasing X angle rotates down.
public static Vector3 Vector3FromAngleYX(float y, float x)
{
float cosx = Mathf.Cos(x);
return new Vector3(cosx * Mathf.Sin(y), -Mathf.Sin(x), cosx * Mathf.Cos(y));
}
Also, I found this question looking to implement a Godot version, so here is a version for Godot Engine written in GDScript, in YX rotation order, treating zero angle as forward (-Z), and follows Godot's conventions of a right-handed, Y-is-up, Z-is-back coordinate system. Increasing Y angle rotates to the left, and increasing X angle rotates up.
func vector3_from_angle_yx(y, x):
var neg_cosx = -cos(x)
return Vector3(neg_cosx * sin(y), sin(x), neg_cosx * cos(y))

Categories