Simple radians on Y axis from Quaternion rotation value.

I can’t figure this out.

I have a transform (player) moving over the surface of a planet. This means that up is sometimes down, and down is sometimes up. All I want is the local Y (up) rotation of a transform as it rotates around the Y axis (player turning left and right). The transform.rotation inverts itself and generally gives me gibberish as I walk around. I would be happy to have radians returned.

For a circle that lives on XY plane, its radius would be a segment going from its center to its circumference, perpendicular to some tangent line. Knowing this, you can always define a radial ray by taking a difference from any surface point and the center.

Vector2 radius = point - center;

Once you have this segment, the gravity of the planet always points toward the center, collinear to radius. However, it is a direction (a vector of magnitude 1).

Vector2 gravity = -radius.normalized;

For a sphere, its radius is very similar, however it is perpendicular to a tangent plane. Still, this segment is once again

Vector3 radius = point - center;

You follow the same principle to get the gravity (or the down direction).

Vector3 gravity = -radius.normalized;

If you now want a quaternion out of this, consider what is up to you. If it’s Vector3.up, then you really want a direction opposite to gravity, so radius.normalized.

Now you use Quaternion.FromToRotation to find the actual rotation for any point on the sphere.

var rotation = Quaternion.FromToRotation(Vector3.up, (point - center).normalized);

However, this won’t work always, namely if your two vectors are antiparallel, the rotation is not defined.
To solve this, you just check whether the two directions are antiparallel, and impose a manual Quaternion that would make Vector3.down from Vector3.up, which is the desired solution.

var globalUp = Vector3.up;
var localUp = (point - center).normalized;
var dot = Vector3.Dot(globalUp, localUp);
if(1f + dot <= 1E-4f) return Quaternion.Euler(180f, 0f, 0f);
return Quaternion.FromToRotation(globalUp, localUp);

I am trying to solve the rotation of the Green Y described in the video.

I can’t see the video (the link is broken).
Also I messed things up. This line should be edit: this change is a complete nonsense lol, just ignore Mathf.Abs (code below this line is reverted)

if(Mathf.Abs(1f + dot) <= 1E-4f) return Quaternion.Euler(180f, 0f, 0f);

All in all, this was supposed to be a function, for example

Quaternion globalRotation(Vector3 point, Vector3 sphereCenter, Vector3 up) {
  var localUp = (point - sphereCenter).normalized;
  var dot = Vector3.Dot(up, localUp);
  if(1f + dot <= 1E-4f) return Quaternion.Euler(180f, 0f, 0f);
  return Quaternion.FromToRotation(up, localUp);
}

You can then simply apply this to anything on your planet.

myCube.localRotation = globalRotation(myCube.position, Vector3.zero, Vector3.up);

Or, if you want to apply it to other vectors, for example, a forward looking eyesight

myEyesightDirection = globalRotation(myBuddy.position, Vector3.zero, Vector3.up) * Vector3.forward;

edit:
Also it’s important to note that Quaternion.Euler(180f, 0f, 0f) only works if your up vector wasn’t collinear with X axis to begin with, so this method can probably be made a bit smarter. I typically use my own version of FromToRotation to which I supply the antiparallel rotation in the call site.

Something like this would suffice (I think)

Quaternion globalRotation(Vector3 point, Vector3 sphereCenter, Vector3 up) {
  var localUp = (point - sphereCenter).normalized;
  var dot = Vector3.Dot(up, localUp);
  if(1f + dot <= 1E-4f) {
    var q = Quaternion.Euler(180f, 0f, 0f);
    if((q * up - up).sqrMagnitude < 1E-7f) q = Quaternion.Euler(0f, 0f, 180f);
    return q;
  }
  return Quaternion.FromToRotation(up, localUp);
}

Mmnah it works, but something’s smelly. I’m sure there’s a more elegant approach, I would have to think some more about it.

Yeah right, you can simply check if the vector was global left or right, that’s better

Quaternion globalRotation(Vector3 point, Vector3 sphereCenter, Vector3 up) {
  var localUp = (point - sphereCenter).normalized;
  var dot = Vector3.Dot(up, localUp);
  if(1f + dot <= 1E-4f) {
    if(up.y == 0f && up.z == 0f) return Quaternion.Euler(0f, 0f, 180f); // a "rare" orthogonal case
    return Quaternion.Euler(180f, 0f, 0f); // this might occur naturally, I mean antiparallels in general
  }
  return Quaternion.FromToRotation(up, localUp); // regular solution
}

edit:
I say “rare” orthogonal case, because it should be rare probability-wise, HOWEVER, it is not rare because it is highly likely such a vector will be used deliberately.

I’ve seen the video now, and yes, that’s the solution I gave you (in post #5), including how to get the orientation around the local Y (the blue arrow; in post #4).

You are free to compound this local Y rotation like this

var someAngle = 45f;
myBuddy.localRotation = globalRotation(...) * Quaternion.Euler(0f, someAngle, 0f);

Sorry about the Mathf.Abs nonsense, this is what sleep deprivation does to humans :slight_smile:
It is completely redundant, I just got scared that I banned a half of the interval. No I did not, but when you write code from your head without checking any source, critical oversights like this one tend to creep up and I tried to cover my bases instead of just thinking straight.