Spherical-based translations from a central point???

Hello,
I was wondering if there is a way to do a transform.Translate, but in a sphere based from a central point instead of a flat surface. I have already tried several various functions including transform.LookAt() to try and do exactly what I wanted, but am not getting the desired results. It would be nice if Unity could develop a simple function like transform.TransformAround(direction : Vector3, pivotalPoint : Vector3) where direction is the direction of the transform, and pivotalPoint would be the central point the transform evolves around.

Another words, If I type…

transform.TransformAround(Vector3(0,-1,0), Vector3(0,0,0));

my character will move closer to Vector3(0,0,0) no matter where it is in world space because the y-axis is decreasing down to the central pivot point based on a spherical environment. I would also like the rotation of the character to change too accordingly as it transforms around a central point. Could there be a function for that as well that would work best with TransformAround() IF and WHEN such a function should exist? Super Mario Galaxy does a perfect example of what I am talking about.

Sincerely,
Michael S. Lowe

Here’s a function similar to what you’re asking, but I suspect your best bet is to read up more on Quaternion and Matrix math before diving in…

Everything you want to accomplish is very doable in Unity, but to complete a game like Mario Galaxy you’ll want a broader understanding of coordinate spaces to save yourself a lot of hair pulling. :slight_smile:

Vector3 TransformAround(Vector3 Origin, float Latitude, float Longitude, float Altitude)
{
	Vector3 coordinate = new Vector3(0f, 0f, -Altitude);
	return Origin + (Quaternion.Euler(Latitude, -Longitude, 0f) * coordinate);
}

Yes but wouldn’t I also need to provide the current position that the player is at ALONG WITH the origin and the direction I would like the player to move in? I would also need to provide it the current direction the player is facing in in order for it to work. transform.Translate() is good because it already figures out where to move to based on the eulerAngles of the object. Wouldn’t it be neat if there really WAS a transform.TranslateAround() function? I am assuming that origin is the pivot point and if so, there would need to be an old position and an old direction.

the function I provided transforms a spherical coordinate (in the form of lon, lat, height) around the origin provided. If you want it to nicely integrate, you can make it an “extension method”, which is a topic worth investigating if you aren’t familiar.

As for a more complex example that moves a player around a planet relative to their orientation, and the planet’s orientation, etc… My best advice is still to read up on the math involved, it will be worth it in the long run to have those skills under your belt. That said, I will attempt a more complex sample in a minute when I’m back on my 'puter. :slight_smile:

Here’s what I came up with… You can place this class in its own file to add a couple extension methods to the Transform class.

You can call them as if they were built-in functions by using transform.MoveAround(PlanetTransform, MovementVector)… The movement vector is in local player units, but is auto-magically converted into spherical rotation… I’m assuming this is closer to what you originally wanted.

I also included a transform.TurnAround() extension to allow the player to turn Left/Right, in case you weren’t sure how to implement this…

public static class Extensions
{
	public static void MoveAround(this Transform self, Transform planet, Vector3 movement)
	{
		//Vector3 relativePosition = planet.InverseTransformPoint(self.position);
		
		Vector3 upVector = self.position - planet.position;
		float height = upVector.magnitude;
		if(height <= 0f)
			return;
		
		float circumference = 2f * Mathf.PI * height;
		
		float unitsToDegrees = 360f / circumference;
		
		Vector3 up = upVector / height;
		Vector3 forward = self.forward;
		Vector3 rightAxis = Vector3.Cross(up, forward).normalized;
		Vector3 forwardAxis = Vector3.Cross(up, rightAxis).normalized;
		
		Quaternion rotation = Quaternion.identity;
		if(movement.z != 0f)
			rotation = Quaternion.AngleAxis(movement.z * unitsToDegrees, rightAxis);
		if(movement.x != 0f)
			rotation *= Quaternion.AngleAxis(movement.x * unitsToDegrees, forwardAxis);
		
		self.position = rotation * (self.position - planet.position) + planet.position + (up * movement.y);
		
		//Maintains Rotational Quirks... if you'd prefer...
		//self.rotation = rotation * self.rotation;
		
		//Forces player to be "correctly" oriented... which is probably what you want...
		self.rotation = rotation * Quaternion.LookRotation(forward, up);
		
		Debug.DrawLine(self.position, self.position+rightAxis*2f, Color.red);
		Debug.DrawLine(self.position, self.position+up*2f, Color.green);
		Debug.DrawLine(self.position, self.position+forward*2f, Color.blue);
	}
	public static void TurnAround(this Transform self, Transform planet, float angle)
	{
		Vector3 upAxis = self.position - planet.position;
		self.rotation = Quaternion.AngleAxis(angle, upAxis) * self.rotation;
	}
}

I hope this helps you get started. :slight_smile:

Thank you. That is exactly what I was looking for! It would be nice if the next Unity release would have that as a built-in function along with the transform.Translate() function they already have where transform.TranslateAround() would require an extra argument called ‘pivot’ or ‘planet’.

I just discovered though that when the player’s euler x angle becomes 90 or 270, THAT’S when problems start happening. The x rotation THEN wants to stay at 90 or 270 and I have to manually set it to 0 or give it a number between 0 and 90 or between 270 and 360 in order for this code to work again, but the good news is that the ONLY time the player’s x rotation can ever become 90 or 270 is when I am constantly applying the line…

MoveAround(transform, world.transform, Vector3(0,0,1));

through my “w” key or …

MoveAround(transform, world.transform, Vector3(0,0,-1));

through my “s” key only when euler angles y and z are approximately at 0.

and never use my “a” and “d” keys (to straff sideways) before noticing that the problem occurs. Another words, if my y and z angles are approximately at 0, and the x angles are being changed (through either 1 of these 2 lines of code above) and the x angle eventually becomes 90 or 270, that’s when I start to notice problems,

I see what you mean. There were a couple quirks with the code that were allowing the player’s rotation to get out of wack with the planet… Here’s the corrected MoveAround method (minus the debug code that shouldn’t have been left in…)

	public static void MoveAround(this Transform self, Transform planet, Vector3 movement)
	{
		Vector3 upVector = self.position - planet.position;
		float height = upVector.magnitude;
		if(height <= 0f)
			return;
		
		float circumference = 2f * Mathf.PI * height;
		
		float unitsToDegrees = 360f / circumference;
		
		Vector3 up = upVector / height;
		Vector3 forward = self.forward;
		Vector3 rightAxis = Vector3.Cross(up, forward).normalized;
		Vector3 forwardAxis = Vector3.Cross(rightAxis, up).normalized;
		
		Quaternion rotation = Quaternion.identity;
		if(movement.z != 0f)
			rotation = Quaternion.AngleAxis(movement.z * unitsToDegrees, rightAxis);
		if(movement.x != 0f)
			rotation *= Quaternion.AngleAxis(-movement.x * unitsToDegrees, forwardAxis);
		
		self.position = rotation * (self.position - planet.position) + planet.position + (up * movement.y);
		
		//Maintains Rotational Quirks...
		//self.rotation = rotation * self.rotation;
		
		//Forces player to be "correctly" oriented...
		self.rotation = rotation * Quaternion.LookRotation(forwardAxis, up);
	}

Thank you so much. I almost forgot to ask what happens if the player wants to turn upwards or downwards. I notice that the TurnAround function takes care of the y rotation spherically but what about x and z, (z being the tilt and x looking up or down from the world)?

To keep things simple, you could make the player’s visuals a child of the main player object… then you could adjust local rotation of the visuals… I only included a “Y” rotation method because X and Z are automatic. Obviously there are limitations, and you aren’t going to be able to write a full blown mario galaxy with these methods alone.

Research, research, research! :slight_smile:

Here is what I came up with. I am using Javascript. I also made Angle a Vector3 (so I can do x and z rotations). The new problem I have is that when x reaches 90 and 270 through the TurnAround() function, problems happen. How did you take care of the last problem with the MoveAround() function? I notice you changed ‘forward’ to ‘forwardAxis’ on the last line. I worked a little on the TurnAround() function, but nothing I do seems to solve the 90/270 angle problem for x. Everything else works.

function TurnAround(self : Transform, planet : Transform, Angle : Vector3) {
  var upAxis : Vector3 = self.position - planet.position;
  var height : float = upAxis.magnitude;
  var up : Vector3 = upAxis * 1.0 / height;
  var forward : Vector3 = self.forward;
  var rightAxis : Vector3 = Vector3.Cross(up, forward).normalized;
  var forwardAxis : Vector3 = Vector3.Cross(rightAxis, up).normalized;

  self.rotation = Quaternion.AngleAxis(Angle.x, rightAxis) * self.rotation;
  self.rotation = Quaternion.AngleAxis(Angle.y, upAxis) * self.rotation;
  self.rotation = Quaternion.AngleAxis(Angle.z, forwardAxis) * self.rotation;

}

I made a few small changes, you’ll need a keen eye to spot since I don’t actually recall.

I do remember that I switched how forwardAxis is calculated and used. :slight_smile:

Here is what I did for the TurnAround() function. Everything works now…

function TurnAround(self : Transform, planet : Transform, Angle : Vector3) {
  var upAxis : Vector3 = self.position - planet.position;
  var height : float = upAxis.magnitude;
  var up : Vector3 = upAxis * 1.0 / height;
  var forward : Vector3 = self.forward;
  var rightAxis : Vector3 = Vector3.Cross(up, forward).normalized;
  var forwardAxis : Vector3 = Vector3.Cross(rightAxis, up).normalized;

  if (self.eulerAngles.y < .5  self.eulerAngles.y >= 0) {
    self.rotation = Quaternion.AngleAxis(.01, upAxis) * self.rotation;
  }
  if (self.eulerAngles.y > -.5  self.eulerAngles.y < 0) {
    self.rotation = Quaternion.AngleAxis(-.01, upAxis) * self.rotation;
  }

  Angle.x *= rightAxis.x;

  self.rotation = Quaternion.AngleAxis(Angle.x, rightAxis) * self.rotation;
  self.rotation = Quaternion.AngleAxis(Angle.y, upAxis) * self.rotation;
  self.rotation = Quaternion.AngleAxis(Angle.z, forwardAxis) * self.rotation;

}

Wouldn’t it be neat if we could also add a MouseMove script and a way to control the player’s movements (also based on the direction of MouseMove.cs) using rigidbody.MovePosition() from an old position to a new position instead of using transform.Translate(), so this way the player deals with physics instead of transforms while it orbits around the globe?

Cool, I’m glad you got it working. :slight_smile:

I’m sure you’ll find a way to integrate it with physics, shouldn’t be too different than scripting regular character movement.

How were you able to make it so that the y rotation gets saved right after TurnAround() gets called and then MoveAround() picks up from wherever the y rotation is at and moves around from there? I notice that when I change rotations x or z through TurnAround() and then call MoveAround(), the rotations x and z become paralell to wherever the player is standing at (making the player stand up straight so that the tilt and the looking upward/downward resets), but when I turn left or right through TurnAround() and then call MoveAround(), the y rotation doesn’t reset, only x and z.

The rotations are ‘saved’ in that the “X” and “Z” axis of rotation is calculated based on the player’s orientation with respect to the planet

Orientation is based on player.forward, which is the player’s forward vector…
The up (axis) is calculated by finding the vector from the planet’s origin to the player…

forwardAxis and rightAxis always lie on the tangent plane of the planet’s surface, so the player is rotated along those axes (placed at the origin of the planet) and maintains/saves the relative orientation… I failed high-school math, so I’m probably not the best person to explain this all… I’ve just learned enough math to get by, and I can tell you that I wish I had payed more attention in school, since it’s a useful skill. :slight_smile:

Grab a 3D math for game development book, and read through it, it’s worth the effort. :slight_smile:

Here is the code for a rigidbody spherical/linear character controller. The console will assist users on what to do. To create a character controller, all you should need to do is attach a child camera to the object/character that uses the script and name it “camera”. Then attach a MouseLook script to both the camera and the main character. The axes for the MouseLook for the camera will automatically be set to MouseY and the MouseLook for the player will automatically be set to MouseX. Everything will work from there.

VObj is the world’s origin and is a GameObject. It will ONLY be useful when vSpherical is set to true or checked. vGravity is the gravity direction and is a Vector3. vTurnSpeed is the speed used when turning. vSpeed is the speed used when moving. Everything else are the keystrokes for turning, moving, tilting or straffing. Without a rigidbody, transform.Translate()s will be used and with a rigidbody, rigidbody.MovePorition()s will be used (for Physics).

#pragma strict

var vObj : GameObject;
var vGravity : Vector3;
var vTurnSpeed : float;
var vSpeed : float;

var vMoveForwardKey : String = "w";
var vMoveBackwardKey : String = "s";
var vMoveLeftKey : String = "a";
var vMoveRightKey : String = "d";
var vMoveUpKey : String = "e";
var vMoveDownKey : String = "c";
var vTurnLeftKey : String = "f";
var vTurnRightKey : String = "h";
var vTiltLeftKey : String = "v";
var vTiltRightKey : String = "n";

var vSpherical : boolean;

@HideInInspector
var xMove : float = 0;
@HideInInspector
var yMove : float = 0;
@HideInInspector
var zMove : float = 0;

function Start () {
  Physics.gravity = Vector3(0,0,0);
  if (!transform.Find("camera")) {
    Debug.Log(name+" does not have a child camera attached to it named 'camera'. This script will treat the object as a non mouse-controlled object instead of a character controller."); 
  } else {
    if (!transform.Find("camera").GetComponent.<MouseLook>()) {
      Debug.Log(name+" has a child camera attached to it called 'camera', but the camera does not have a script attached called 'MouseLook'. This script will treat the object as a non mouse-controlled object instead of a character controller.");
    } else {
      transform.Find("camera").GetComponent.<MouseLook>().axes = 2;
    }
  }
  if (!GetComponent.<MouseLook>()) {
    Debug.Log(name+" does not have a MouseLook script attached to it. You may still use the character controlls to turn left and right, but it is recommended that both the object and a chld camera has a MouseLook script attached so the player can also look up and down as well.");
  } else {
    GetComponent.<MouseLook>().axes = 1;  
  }
  if (!rigidbody) {
    Debug.Log(name+" does not have a rigidbody attached to it. This script will use transformed movements instead of physics."); 
  }
}

function FixedUpdate () {
  if (vSpherical) {
    updateSphereControls(); 
  } else {
    updateLinearControls();
  }
}

function Move(self : Transform, movement : Vector3) : void {
  self.Translate(movement);
}

function Turn(self : Transform, Angle : Vector3) : void {
  self.Rotate(Angle);
}

function MoveAround(self : Transform, planet : Transform, movement : Vector3) : void {
  
  var pos : Vector3 = self.position;
  var pos2 : Vector3;
  
  var upVector : Vector3 = self.position - planet.position;
  var height : float = upVector.magnitude;

  if (height <= 0) { 
    return;
  }
        
  var circumference : float = 2.0 * Mathf.PI * height;
  var unitsToDegrees : float = 360.0 / circumference;

  var up : Vector3 = upVector * 1.0 / height;
  var forward : Vector3 = self.forward;
  var rightAxis : Vector3 = Vector3.Cross(up, forward).normalized;
  var forwardAxis : Vector3 = Vector3.Cross(rightAxis, up).normalized;

  var rotation : Quaternion = Quaternion.identity;

  if(movement.z != 0) { 
    rotation *= Quaternion.AngleAxis(movement.z * unitsToDegrees, rightAxis);
  }
  
  if(movement.x != 0) { 
    rotation *= Quaternion.AngleAxis(-movement.x * unitsToDegrees, forwardAxis);
  }
  
  self.position = rotation * (self.position - planet.position) + planet.position + (up * movement.y);

  self.rotation = (rotation * Quaternion.LookRotation(forwardAxis, self.up));
  
  
  if (rigidbody) {
    pos2 = self.position;
    rigidbody.position = pos;
    rigidbody.MovePosition(pos2);
  }
  
}

function TurnAround(self : Transform, planet : Transform, Angle : Vector3) {
  var upAxis : Vector3 = self.position - planet.position;
  var height : float = upAxis.magnitude;
  var up : Vector3 = upAxis * 1.0 / height;
  var forward : Vector3 = self.forward;
  var rightAxis : Vector3 = Vector3.Cross(up, forward).normalized;
  var forwardAxis : Vector3 = Vector3.Cross(rightAxis, up).normalized;

  if (self.eulerAngles.y < .5  self.eulerAngles.y >= 0) {
    self.rotation = Quaternion.AngleAxis(.01, upAxis) * self.rotation;
  }
  if (self.eulerAngles.y > -.5  self.eulerAngles.y < 0) {
    self.rotation = Quaternion.AngleAxis(-.01, upAxis) * self.rotation;
  }

  Angle.x *= rightAxis.x;

  self.rotation = Quaternion.AngleAxis(Angle.x, rightAxis) * self.rotation;
  self.rotation = Quaternion.AngleAxis(Angle.y, upAxis) * self.rotation;
  self.rotation = Quaternion.AngleAxis(Angle.z, forwardAxis) * self.rotation;

}

function updateSphereControls() : void {
  MoveAround(transform, vObj.transform, vGravity);
  
 
  if (transform.Find("camera")) { 
    if (transform.Find("camera").GetComponent.<MouseLook>()) { 
      if (vMoveForwardKey != "") {
        if (Input.GetKey(vMoveForwardKey)) {
          MoveAround(transform, vObj.transform, Vector3(0,-Mathf.Sin(transform.Find("camera").transform.localEulerAngles.x * Mathf.PI / 180),vSpeed*Mathf.Cos(transform.Find("camera").transform.localEulerAngles.x * Mathf.PI / 180)));
        }
      }
      if (vMoveBackwardKey != "") {
        if (Input.GetKey(vMoveBackwardKey)) {
          MoveAround(transform, vObj.transform, Vector3(0,Mathf.Sin(transform.Find("camera").transform.localEulerAngles.x * Mathf.PI / 180),-vSpeed*Mathf.Cos(transform.Find("camera").transform.localEulerAngles.x * Mathf.PI / 180)));
        }
      }
    } else {
      if (Input.GetKey(vMoveForwardKey)) {
        if (vMoveForwardKey != "") {
          MoveAround(transform, vObj.transform, Vector3(0,0,vSpeed));
        }
      }
      if (Input.GetKey(vMoveBackwardKey)) {
        if (vMoveBackwardKey != "") {
          MoveAround(transform, vObj.transform, Vector3(0,0,-vSpeed));
        }
      }
    }
  } else {
    if (Input.GetKey(vMoveForwardKey)) {
      if (vMoveForwardKey != "") {
        MoveAround(transform, vObj.transform, Vector3(0,0,vSpeed));
      }
    }
    if (Input.GetKey(vMoveBackwardKey)) {
      if (vMoveBackwardKey != "") {
        MoveAround(transform, vObj.transform, Vector3(0,0,-vSpeed));
      }
    }
  }
  if (Input.GetKey(vMoveLeftKey)) {
    if (vMoveLeftKey != "") {
      MoveAround(transform, vObj.transform, Vector3(vSpeed,0,0));
    }
  }
  if (Input.GetKey(vMoveRightKey)) {
    if (vMoveRightKey != "") {
      MoveAround(transform, vObj.transform, Vector3(-vSpeed,0,0));
    }
  }
  if (Input.GetKey(vMoveUpKey)) {
    if (vMoveUpKey != "") {
      MoveAround(transform, vObj.transform, Vector3(0,vSpeed,0));
    }
  }
  if (Input.GetKey(vMoveDownKey)) {
    if (vMoveDownKey != "") {
      MoveAround(transform, vObj.transform, Vector3(0,-vSpeed,0));
    }
  }
  
  if (Input.GetKey(vTurnRightKey)) {
    if (vTurnRightKey != "") {
      TurnAround(transform, vObj.transform, Vector3(0,vTurnSpeed,0)); 
    }
  }
  if (Input.GetKey(vTurnLeftKey)) {
    if (vTurnLeftKey != "") {
      TurnAround(transform, vObj.transform, Vector3(0,-vTurnSpeed,0)); 
    }
  }
  if (Input.GetKey(vTiltLeftKey)) {
    if (vTiltLeftKey != "") {
      TurnAround(transform, vObj.transform, Vector3(0,0,vTurnSpeed)); 
    }
  }
  if (Input.GetKey(vTiltRightKey)) {
    if (vTiltRightKey != "") {
      TurnAround(transform, vObj.transform, Vector3(0,0,-vTurnSpeed)); 
    }
  }
}

function updateLinearControls() : void {
  Move(transform, vGravity);
  
 
  if (transform.Find("camera")) { 
    if (transform.Find("camera").GetComponent.<MouseLook>()) { 
      if (Input.GetKey(vMoveForwardKey)) {
        if (vMoveForwardKey != "") {
          Move(transform, Vector3(0,-Mathf.Sin(transform.Find("camera").transform.localEulerAngles.x * Mathf.PI / 180),vSpeed*Mathf.Cos(transform.Find("camera").transform.localEulerAngles.x * Mathf.PI / 180)));
        }
      }
      if (Input.GetKey(vMoveBackwardKey)) {
        if (vMoveBackwardKey != "") {
          Move(transform, Vector3(0,Mathf.Sin(transform.Find("camera").transform.localEulerAngles.x * Mathf.PI / 180),-vSpeed*Mathf.Cos(transform.Find("camera").transform.localEulerAngles.x * Mathf.PI / 180)));
        }
      }
    } else {
      if (Input.GetKey(vMoveForwardKey)) {
        if (vMoveForwardKey != "") {
          Move(transform, Vector3(0,0,vSpeed));
        }
      }
      if (Input.GetKey(vMoveBackwardKey)) {
        if (vMoveBackwardKey != "") {
          Move(transform, Vector3(0,0,-vSpeed));
        }
      }
    }
  } else {
    if (Input.GetKey(vMoveForwardKey)) {
      if (vMoveForwardKey != "") {
        Move(transform, Vector3(0,0,vSpeed));
      }
    }
    if (Input.GetKey(vMoveBackwardKey)) {
      if (vMoveBackwardKey != "") {
        Move(transform, Vector3(0,0,-vSpeed));
      }
    }
  }
  if (Input.GetKey(vMoveLeftKey)) {
    if (vMoveLeftKey != "") {
      Move(transform, Vector3(-vSpeed,0,0));
    }
  }
  if (Input.GetKey(vMoveRightKey)) {
    if (vMoveRightKey != "") {
      Move(transform, Vector3(vSpeed,0,0));
    }
  }
  if (Input.GetKey(vMoveUpKey)) {
    if (vMoveUpKey != "") {
      Move(transform, Vector3(0,vSpeed,0));
    }
  }
  if (Input.GetKey(vMoveDownKey)) {
    if (vMoveDownKey != "") {
      Move(transform, Vector3(0,-vSpeed,0));
    }
  }
  
  if (Input.GetKey(vTurnRightKey)) {
    if (vTurnRightKey != "") {
      Turn(transform, Vector3(0,vTurnSpeed,0)); 
    }
  }
  if (Input.GetKey(vTurnLeftKey)) {
    if (vTurnLeftKey != "") {
      Turn(transform, Vector3(0,-vTurnSpeed,0)); 
    }
  }
  if (Input.GetKey(vTiltLeftKey)) {
    if (vTiltLeftKey != "") {
      Turn(transform, Vector3(0,0,vTurnSpeed)); 
    }
  }
  if (Input.GetKey(vTiltRightKey)) {
    if (vTiltRightKey != "") {
      Turn(transform, Vector3(0,0,-vTurnSpeed)); 
    }
  }
}