how to do Quaternion.RotateTowards on single LOCAL axis?
I've found several solutions for achieving this goal when working from global axises (I hope that's spelled right). But have a look at my picture and you'll see that this wont work for me :\
I want that red character rotate towards the blue one back there. But I don't want him to fall over or do other wiered stuff because of my FauxGravity to the sphere. (the FauxGravity script I use forces every linked body to rotate so it stands on the surface corectly)
EDIT: I visualized what i want to happen in a small clip: http://gfycat.com/SingleSparseBarracuda
I should downvote for using my avatar... lol just kidding :P
Have you looked into http://docs.unity3d.com/ScriptReference/Vector3.RotateTowards.html ? You can try their example, I don't remember if it will rotate every axis (probably). If it rotates everything, I would try using the example, but then extracting only the axis you want. $$anonymous$$aybe?
Hope this helps, at least put you on the right track :)
hmmm... SANDWICH!!
I had a look at that allready, but I'll have another one just in case.
I have a feeling you want to save the 2 other axes, and then set those back after the looktowards... It's an insteresting problem for sure, be sure to post your solution when you find it :)
But I love TACOS! I need them or I will EXPLODE ^_^
Answer by Statement · Oct 26, 2015 at 10:22 PM
EDIT: Fixed some issues and reduced the math a bit.
There may be other ways to solve it but my math is a bit rusty. However, one approach would be something like this. Given the two dudes A and B (red and blue. image 1), we can make a vector C (green, image 2) which is A - B.
We can project C onto the plane of B to get a point D (pink, image 4).
This D point is in world space. It would be nice if it was in local space to B where rotation can be performed along Y axis, so let's convert it (square, image 5). Now we can ignore Y and work with X and Z. From X, Z we can use simple trig like Mathf.Atan2 to get an angle. Now we have a single angle that defines Y rotation, so all we need to do is keep moving an angle toward the destination angle.
From this angle, we can make a quaternion that is a Y rotation.
Then we can apply the Y rotation to your existing rotation, to preserve your plants Up direction.
This all sounds crazy and stuff with math and ideas, so how do you do this in code?
Vector3 LookatXZ()
{
Vector3 distance = lookat.position - transform.position;
Vector3 direction = Vector3.ProjectOnPlane(distance, transform.up).normalized;
return transform.InverseTransformVector(direction);
}
LookatXZ will perform the steps to get the direction in local space as described above. We can ignore the Y component and focus on X,Z, which will be the direction toward the target on our plane.
private static float AngleXZ(Vector3 direction)
{
return Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg;
}
We can then take that direction and use Atan2 to get radians that describe our rotation. But quaterion methods expect degrees, not radians, so I convert the radians to degrees with Rad2Deg. What we get back now is an angle 0-360 degrees, which is the Y rotation that should be applied.
If we want to look toward a target gradually, we can use Mathf.MoveTowardsAngle which will take care of wrapping negative values for us (-180 is the same as 180 etc). Finally, we can create the quaternion based on the Y axis.
void LateUpdate()
{
if (!lookat)
return;
float forwardAngle = AngleXZ(ForwardXZ());
float lookatAngle = AngleXZ(LookatXZ());
float speed = degreesPerSecond * Time.deltaTime;
float angle = Mathf.MoveTowardsAngle(forwardAngle, lookatAngle, speed);
transform.rotation *= Quaternion.AngleAxis(angle, Vector3.up);
}
I've got an example project (based on your previous question, but I disabled the line renderer) so you can test it out. Code file is linked too if you just want to set it up on your end. Note that rotation is not interactive in scene view in the example project. You have to press play and then drag the "Drag Me" around in the scene view. Both of them should be trying to look at each other.
Dragging is a little buggy so I advise you to change from local transform handles to world transform handles (toggle with X key)
EDIT: Updated links to point to new version with fixes.
If you later want to move your characters on the sphere, look at this existing answer if you don't have ideas.
jesus, your answers are bonkers! Are you getting payed for this? you really should be! :D
you will have a place in the credits of my project 100% (if you don't $$anonymous$$d that)
and thank you very much for explaining the stuff so well. $$anonymous$$y understanding of math is waaay worse than yours seems to be and I understood this all so well that I think to know that this will work just by reading.
No, I don't get paid for this. I just like to elaborate on answers. I think we have enough of "here are the docs, RTF$$anonymous$$" kind of answers. Yes, I agree that feeding people solutions might turn them into zombie programmers but I am personally that kind of guy who appreciate some snippets, learn from it, improve on it and adapt. Also, it refreshes my $$anonymous$$d on math and problem solving. Though, don't expect to get these kinds of detailed answers every time and from every one. It's just my pastime. I'm rubbish at ideas so I try to do what other people dream up.
yea, I think I can understand that. This project infact wasn't really my own idea. I came up with it after I saw a similar project at a friends place. Your answers certainly have been a great learning experience so far. I hope I can live up to this example one day.
Eh, you can just do a "Thanks to the Unity Community" thing.
okay I'm done implementing and it is working. But,..
Have a look at this: gfycat.com/Specific$$anonymous$$ean$$anonymous$$itten
I tried fixing it by adding an if statement to stop the rotation. But the character doesn't stop at the right angle because of its idk, 'swing'?
if (transform.rotation == transform.rotation * yRotation) {
actionNr = -1;
}
transform.rotation *= yRotation;
I need to know when I can stop excecuting the whole thing, because it takes a good amount of performance and I want the character move towards the target after rotating (The moving part is allready figured out (I think).)
just had a look at your example project. it doesn't happen there. It's because I have a rigidbody component on my characters. I need that to make my FauxGravity script work.
EDIT: I guess, I could get rid of that. I will try some things, and come back.
EDIT: I managed to get rid of the rigidbody but then my colliders (obviously) stopped working. I need those colliders to create those spherical lines when the characters meet each other. Found several questions adressing this but everybody just tell that I'll need atleast one character having a rigidbody to make the colliders work. But both characters will be moving later and changing the rigidbody behaviour to kinematic doesn't prevent the spinning and drag would mess with the rotation speed.
Next, I updated that if statement by setting the angularVelocity to zero but that only faced me to the fact that this if statement isn't working at all. the character just makes one bit of the whole turn and then stops, way before being at the target rotation. (I guess I didnt understand your code as well as I thought.
The swinging motion happen because you stop updating the rotation from your other script I think. It shouldn't happen just because you have a rigidbody.
You can stop updating the rotation after currentAngle == angle.
It's a fragile solution that depends upon the other script setting the rotation every frame. Otherwise rotation will start accumulating and you get that pendulum effect.
You could maybe solve this with something like this: transform.rotation = fauxthingey.theRotationILastSet * yRotation;
Your fauxthingey was setting the rotation, right? So it knew how to create the rotation. But you stop updating the transforms rotation I guess. Then use the rotation you used in the other script as a base rotation for your yRotation. See if that works.
I'm not sure I understand. However my FauxGravity script keeps updating the rotation.
On the character:
[RequireComponent(typeof(Rigidbody))] //rigidbody approach
public class FauxGravityBody : $$anonymous$$onoBehaviour
{
private Rigidbody rb; //rigidbody approach
public FauxGravityAttractor attractor;
private Transform myTransform;
void Start()
{
rb = GetComponent<Rigidbody>(); //rigidbody approach
rb.useGravity = false; //rigidbody approach
rb.constraints = RigidbodyConstraints.FreezeRotation; //rigidbody approach
myTransform = transform;
}
void FixedUpdate()
{
if (attractor)
{
attractor.Attract(myTransform);
}
}
}
On the sphere:
public class FauxGravityAttractor : $$anonymous$$onoBehaviour
{
public float gravity = -12;
//snapTransforms approach
//public bool snapBodys = true;
//public float radius = 1f;
//
//void Awake() {
// radius = transform.localScale.x / 2;
//}
public void Attract(Transform body)
{
Vector3 gravityUp = (body.position - transform.position).normalized;
Vector3 localUp = body.up;
//rigidody approach
body.GetComponent<Rigidbody>().AddForce(gravityUp * gravity);
//snapTransforms approach
//if (snapBodys)
//{
// Vector3 localTo = (body.position - transform.position).normalized * radius;
// body.position = localTo;
//}
Quaternion targetRotation = Quaternion.FromToRotation(localUp, gravityUp) * body.rotation;
body.rotation = Quaternion.Slerp(body.rotation, targetRotation, 50f * Time.deltaTime);
}
}
I found a math error. I've updated the links and edited the answer. I also added some Gizmos so you can visualize the vectors involved. You can remove all the gizmo functions if you don't need the visualization when you select the object. It doesn't affect the functionality of the rotations.
This is working great! now I still need to find a way to break out of that loop. have been trying around with that allready but.. (the Debug message never appears)
if (actionStep != 0)
{
rb.velocity = transform.forward * moveSpeed;
}
else
{
// SphericalLookTowards Unity-Answers-Statement-27-10-2015
float forwardAngle = AngleXZ(ForwardXZ());
float lookatAngle = AngleXZ(LookatXZ(tempCollider.transform));
float speed = moveSpeed * Time.deltaTime;
float angle = $$anonymous$$athf.$$anonymous$$oveTowardsAngle(forwardAngle, lookatAngle, speed);
if (forwardAngle < lookatAngle)
{
transform.rotation *= Quaternion.AngleAxis(angle, Vector3.up);
if (forwardAngle >= lookatAngle)
{
actionStep = 1;
}
}
else
{
transform.rotation *= Quaternion.AngleAxis(angle, Vector3.up);
if (forwardAngle <= lookatAngle)
{
Debug.Log("doing Action 1, Step 2..");
actionStep = 1;
}
}
}
Debugging the whole thing confuses me. what am I doing wrong?
Frame: 563 # forwardAngle = 0
Frame: 563 # lookatAngle = -0.6545103
Frame: 567 # forwardAngle = 0
Frame: 567 # lookatAngle = -0.05968561
Frame: 570 # forwardAngle = 0
Frame: 570 # lookatAngle = 8.537736E-06
Frame: 574 # forwardAngle = 0
Frame: 574 # lookatAngle = -3.415095E-06
Frame: 577 # forwardAngle = 0
Frame: 577 # lookatAngle = -3.415095E-06....................
EDIT : Fixed! I simply had to add a script excecution order. If 'FauxGravityBody' is set to run after 'SphericalLookTowards' my if statement works, and the character starts moving.
Is it wise to to it that way? there are numbers I can change in the script excecution order and I dont really know what that means.
EDIT: Actually, it doesn't fix it. it just sometimes work and sometimes doesn't :\ woot!?
also tried enabling and disabling the FauxGravityBody rotation during the process of SphericalLookTowards, no luck. still sometimes happens, sometimes not.
Done! it's kinda dirty, but it works. And it keeps working. your last comment wasn't the true fix (it was working more often but still stopped working sometimes) but it brought me on the right way.
float forwardAngle = AngleXZ(ForwardXZ());
float lookatAngle = AngleXZ(LookatXZ(tempCollider.transform));
float speed = rotateSpeed * Time.deltaTime;
float angle = $$anonymous$$athf.$$anonymous$$oveTowardsAngle(forwardAngle, lookatAngle, speed);
float lastAngleRound = (float)System.$$anonymous$$ath.Round(lastAngle, 5);
float nextlastAngleRound = (float)System.$$anonymous$$ath.Round(nextlastAngle, 5);
float lookatAngleRound = (float)System.$$anonymous$$ath.Round(lookatAngle, 5);
if (nextlastAngleRound != lookatAngleRound) {
if (lastAngleRound != lookatAngleRound) {
transform.rotation *= Quaternion.AngleAxis(angle, Vector3.up);
nextlastAngle = lastAngle;
lastAngle = lookatAngle;
}
else {
lastAngle = 361f;
nextlastAngle = 361f;
actionStep = 1;
}
}
else {
lastAngle = 361f;
nextlastAngle = 361f;
actionStep = 1;
}
angle and lookAtAngle will sometimes never be the same number. And none of them will allways be 0 when at end rotation. sometimes they will also switch around from one value to another forever and ever.
Gotta think now how I can make this all clean (and maybe a bit faster too). But I think we're done here for now.
Thank you so much Statement!
Your answer
![](https://koobas.hobune.stream/wayback/20220612042827im_/https://answers.unity.com/themes/thub/images/avi.jpg)