Rotate rigid body in one direction with AddTorque - unity3d

I have a main camera that rotates around an object (which I already move with AddForce). Using AddTorque, I would like the object to rotate its Y axis of rotation in the direction in which the camera's Y axis of rotation points so as to simulate the rotation of a person turning around. To do this I thought of using a force that gave me 0 when the Y axes of the two objects are the same, but I tried and the rigidbody covers only one part of the rotation of the camera while the other part is as if it not is detected.
var currentR = rb.rotation.y;
var targetR = Camera.main.transform.rotation.y;
rb.AddTorque(transform.up * 1000f * (targetR - currentR));
rotation my object
desired rotation

Vector3 camForward = Camera.main.transform.forward;
Vector3 rbForward = rb.transform.forward;
Vector3 torque = Vector3.Cross(camForward, rbForward);
rb.AddTorque(torque);

Related

Rotate a quaternion / Change the axis it rotates around

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.

How to make the camera track from behind a target object in Unity

I have to make camera track a sphere object from behind it.
The camera should capture it in middle while face same direction of the object's velocity direction.
And the distance between the camera and the object should be always same.
I could make the camera face same direction of the object's velocity direction by this code.
Quaternion lookRotation = Quaternion.LookRotation(targetRb.velocity);
lookRotation.z = 0;
lookRotation.x = 0;
camera.transform.rotation = Quaternion.Lerp(camera.transform.rotation, lookRotation, 0.4f);
But I can't find how to calcurate the position where the camera should be.
Could you tell me how to do this?
You could add e.g.
var targetPosition = targetRb.position - targetRb.velocity.normalized * DESIRED_OFFSET;
camera.transform.position = Vector3.Lerp(camera.transform.position, targetPosition, 0.4f);
which always moves camera towards a position DESIRED_OFFSET units behind the target.
or alternatively you could use the camera's current direction if you really want the sphere to always be fix in the center
var targetPosition = targetRb.position - camera.transform.forward * DESIRED_OFFSET;
camera.transform.position = targetPosition;
Or you could also Lerp this one again. I guess you have to try a few options and see what fits your desired outcome best.

Getting compass-like behavior from quaternion

Suppose you have a camera projection matrix, i.e. camera translation vector + rotation quaternion, like every typical camera, it is able to move and rotate in any direction. And independent of it's rotation whether it is looking forward, upward or downward I need to show a compass-like gauge pointing where the camera is targeted at.
The problem is that when the camera is pointed downwards the rotation of camera around it's optical center defines the value of the compass, but when the camera points forward, the rotation of camera around it's center no longer affects the value of compass, in this case the direction of camera defines the value of compass.
It get's more ugly when the camera is tilted downwards only 45 degrees, in this case it is not even clear whether the rotation around camera center affects rotation of compass.
So is there an elegant way of getting the compass value based on arbitrary camera projection matrix / quaternion?
Thank you in advance!
If you want just an arrow pointing at the target its:
Transform camera = Camera.main.transform;
Transform target = Target.transform;
Vector3 relativePosition = target.position - camera.position;
Vector3 targetRelative = Vector3.ProjectOnPlane(relativePosition, camera.forward);
float angle = Angle360(camera.up, targetRelative, camera.forward);
Compass.transform.rotation = Quaternion.Euler(0, 0, angle);
The angle function is:
float Angle360(Vector3 from, Vector3 to, Vector3 normal)
{
float dot = Vector3.Dot(from, to);
float det = Vector3.Dot(normal, Vector3.Cross(from, to));
return Mathf.Atan2(det, dot)*Mathf.Rad2Deg;
}
Here is how you can get the direction of the compass in worldspace:
Project the camera direction and target position on the XZ plane
Transform camera = Camera.main.transform;
Transform target = Target.transform;
Vector3 cameraWorldDirXZ = Vector3.ProjectOnPlane(camera.forward, Vector3.up).normalized;
Vector3 targetWorldDirXZ = Vector3.ProjectOnPlane(target.position, Vector3.up).normalized;
The angle between the cameraWorldDirXZ and targetWorldDirXZ is the angle of your compass needle.
But i don't think this will behave like you think it will. This gives you the angle that you need to rotate the camera.forward vector around the y axis to face the target. If you rotate around camera.forward you don't change either the camera.forward vector or the y axis so the compass wont change.
You might want to try a compass in local space. For that you project onto the camera XZ plane:
Vector3 cameraLocalDirXZ = camera.forward;
Vector3 targetLocalDirXZ = Vector3.ProjectOnPlane(target.position, camera.up).normalized;
Again the angle between the cameraLocalDirXZ and targetLocalDirXZ is the angle of your compass needle. This gives you the angle you need to rotate camera.forward around camera.up to face the target. Note that when you rotate around camera.forward it will change camera.up so it will change the compass direction.
If anyone stumbles upon this problem, the solution (thanks to #Pluto) is very simple, multiply your camera quaternion over three axis vectors (0,0,1), (0,1,0), (1,0,0), you will get three vectors defining coordinate system of your camera, project those three vectors onto your plane, find centroid of your three projected points and voila you have compass direction.
Here's the piece of code for that:
var rotation = /* Your quaternion */;
var cameraOrtX = rotation * new Vector3 (1, 0, 0);
var cameraOrtY = rotation * new Vector3 (0, 1, 0);
var cameraOrtZ = rotation * new Vector3 (0, 0, 1);
var cameraOrtPX = Vector3.ProjectOnPlane(cameraOrtX, new Vector3(0, 1, 0));
var cameraOrtPY = Vector3.ProjectOnPlane(cameraOrtY, new Vector3(0, 1, 0));
var cameraOrtPZ = Vector3.ProjectOnPlane(cameraOrtZ, new Vector3(0, 1, 0));
var centroid = (cameraOrtPX + cameraOrtPY + cameraOrtPZ) / 3.0f;

Determining if quarternion rotation is clockwise or counter clockwise

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;

Rotate an object around its center point in unity 3d

How to rotate a 3d game object around its center point in unity 3d.
Just use bounds.center from the renderer
Vector3 position = myGameObject.GetComponent<Renderer>().bounds.center;
myGameObject.transform.RotateAround(position, rotationVector, degreesPerSecond * Time.deltaTime);
where rotationVector is your rotation axis (Vector3)
The two common ways to rotate an object are
The rotate attribute in the transform. Using this one you can set the exact coordinates for the target object rotation. However, you'll have to manage the smoothness by yourself if you want to make animations and the values are given through Quaternion type. I recommend to use the static method Quaternion.Euler so you can pass values in a X, Y, Z. The example below set the object to turn 90 degrees clockwise in the Y axis:
transform.rotation = Quaternion.Euler (0, 90, 0);
The second approach uses the Rotation method in the same transform attribute. This method allow you to pass the amount of degrees in which object will rotate and already accept X, Y and Z coordinates instead Quaternion.
The example below rotate the object 1 degree clockwise in the Y axis:
transform.Rotate (0, 1, 0);
To best understand the difference between both methods, if you use the first one in an Update method you'll see the object static rotated 90 degrees in the Y axis. The second example used in an Update will make the object spin clockwise in the Y axis (too fast).
use
Transform.rotation
look here for Examples/Documentation :
Unity Script Reference