Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question about Quaternions

Discussion in 'Editor & General Support' started by joe gamble, Jan 9, 2007.

  1. joe gamble

    joe gamble

    Joined:
    Jan 3, 2007
    Posts:
    85
    I am coding a game where the game pieces reside on the outside of a sphere. I've set up code so that a force is exerted toward the center of the sphere, like the following.

    function FixedUpdate()
    {
    var relativePos = transform.position;
    var rotation = Quaternion.FromToRotation (Vector3.up, relativePos);
    transform.rotation = rotation ;

    rigidbody.AddForce(-relativePos * 9.8);
    }

    The pieces have a top and bottom and the bottom always points toward the center of the sphere. The pieces also need to be able to move on the sphere always keeping there orintation towards the center of the sphere. The pieces also need to be able to turn around the y axis as they move. The above code accomplishes part of what I want but I'm still having problems getting the pieces to move properly and I can't get them to rotate around the y axis.

    I've tried different approaches to this and can't seem to get the desired movement.

    So is the above a good aproach to this or am I totally missing it and need to try a new approach. Any help here is appreciated.
     
  2. pete

    pete

    Joined:
    Jul 21, 2005
    Posts:
    1,647
    i'd guess you'd want to do something like set your Vector3.up from the surface normal of the sphere. not quite sure on the code. this is from the script ref.

    Code (csharp):
    1. // Sets the rotation so that the transform's y-axis follows the surface normal transform.rotation = Quaternion.FromToRotation (Vector3.up, surfaceNormal);
    i can't find another ref to surfaceNormal. so i'm not sure if it auto finds the normal of the poly below or if you have to do a raycast and get the Vector3.

    apply the down force like you did. then Input.GetAxis horizontal and vertical for forward movement and rotation around y.

    this script i just put on the wiki isn't really what you want but it has rotation of an object through keys in it. i'd think the fpswalker should work for movement (may need to edit it to fit).
    http://www.unifycommunity.com/wiki/index.php?title=MouseLookPlus
     
  3. joe gamble

    joe gamble

    Joined:
    Jan 3, 2007
    Posts:
    85
    thanks, I'll try that.
     
  4. pete

    pete

    Joined:
    Jul 21, 2005
    Posts:
    1,647
    Code (csharp):
    1. function Update () {
    2. var hit : RaycastHit;
    3. var surfaceNormal : Vector3;
    4.  
    5. if (Physics.Raycast (transform.position, Vector3.down, hit)) {
    6. surfaceNormal = hit.Vector3;
    7. }
    8.  
    9. transform.rotation = Quaternion.FromToRotation(Vector3.up, surfaceNormal.normal);
    10. }
    11.  
    12. function FixedUpdate(){
    13.  var relativePos = transform.position;
    14.  rigidbody.AddForce(-relativePos * 9.8);
    15. }
    16.  
    i think surfaceNormal in the docs is just a variable that wasn't declared in the example (someone say so if i'm wrong). so something along the above should rotate you around the sphere surface. i didn't try it.

    [edit2: oops! i typo'd and my first edit was simply goofy! hope you didn't already read it!]
     
  5. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,773
    Code (csharp):
    1.  
    2. rigidbody.AddForce( (centerObject.position - transform.position).normalized * 9.8);
    3.  
    where centerObject is the transform of the object you need to gravitate towards.
     
  6. joe gamble

    joe gamble

    Joined:
    Jan 3, 2007
    Posts:
    85
    Here is the solution that I came up with that seems to work everywhere on the sphere exept around the south pole. While navigating near the south pole area
    the game piece no longer moves in a straight line but in spirals toward the center point. And at the center point of the pole it just sits and spins. I'll work that out later.




    private var ang = 0;

    private var gravity = 2;
    private var angle = 0;
    private var trans = 0;

    rigidbody.freezeRotation = true;

    function FixedUpdate()
    {
    // for now I'm assuming the center point of the sphere to be 0,0,0
    // I'll add center object later after I work some of the bugs out.
    var relativePos = transform.position;

    // this keeps the orietation of the object always pointing perpendicular to
    //the surface of the sphere

    var rotation = Quaternion.FromToRotation (Vector3.up, relativePos);
    transform.rotation = rotation;

    var rotationSpeed = 1;
    var speed = 1;

    // Get the horizontal and vertical axis.
    // By default they are mapped to the arrow keys.
    // The value is in the range -1 to 1

    var trans = Input.GetAxis ("Vertical") * speed;
    var ang = Input.GetAxis ("Horizontal") * rotationSpeed;

    // accumulate the angle depending on how many times they
    // rotated left or right

    angle+=ang;

    // rotate it around the y axis
    transform.Rotate(0,angle,0);

    // translate it forward or backward
    transform.Translate (Vector3.right * -trans);

    // and apply gravity
    rigidbody.AddForce(-relativePos * gravity);
    }



    The ray cast approach didn't work very well and was a bit cpu hungry

    thanks for the help. It's mostly working now.
     
  7. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,773
    It should be noted that it's not recommended to combine physics (AddForce) and manually setting positions (like transform.rotation and transform.Translate).

    Code (csharp):
    1.  
    2. rigidbody.velocity = transform.rotation * Vector3.right * speed;
    3. rigidbody.angularVelocity = transform.rotation * Quaternion.AngleAxis(angle, relativePos);
    4.  
    (The above code will cancel any previous AddForce, i.e. your gravity; compensating for that is left as an exercise for the user ;) )

    If you do use manual settings, you should also be multiplying all of your movements (rotation and translation) by Time.deltaTime; otherwise, they will be framerate-dependent and gameplay will change drastically depending on how fast your user's computer is running.

    I'm not sure off the top of my head what could be causing your south pole problem. Have you turned global gravity off in the Physics preferences? Interplay between that and your own simulated gravity could be problematic.
     
  8. joe gamble

    joe gamble

    Joined:
    Jan 3, 2007
    Posts:
    85
    that last line doesn't compile.

    rigidbody.angularVelocity = transform.rotation * Quaternion.AngleAxis(angle, relativePos);


    error BCE0022: Cannot convert 'UnityEngine.Quaternion' to 'UnityEngine.Vector3'
     
  9. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,773
    Whoops, my mistake - I wrote that without testing it, and forgot angularVelocity needs to be Vector3.

    Code (csharp):
    1.  
    2. rigidbody.velocity = transform.rotation * Vector3.right * speed;
    3. rigidbody.angularVelocity = (transform.rotation * Quaternion.AngleAxis(angle, relativePos)).eulerAngles;
    4.  
     
  10. joe gamble

    joe gamble

    Joined:
    Jan 3, 2007
    Posts:
    85
    After further testing I've noticed something rather odd.

    You know how I mentioned the south pole problem above. Well the movement problem isn't limited to only the being at the south pole. If I move the piece in a straight line in any direction it will always curve toward the bottom of the sphere. eventually intersecting the point where x and z are both 0. Now that is the point where the piece starts spinning. I'm overly concerned with the spinning but not being able to move in a straight line is problematic. I've ruled out the rigidbody.addForce as the culprit. I can take that line out completely and still be able to move in a spherical manner. And I have disabled gravity everywhere. I think the culprit is
    Quaternion.FromToRotation (Vector3.up, -relativePos).

    Also tested changing the orietation of the sphere. It made no difference how the sphere was oriented.

    Any ideas here? Is there an alernate way of keeping the y axis of the game piece always oriented toward the center of the sphere?
     
  11. joe gamble

    joe gamble

    Joined:
    Jan 3, 2007
    Posts:
    85
    interesting thing I just tried.

    if I flip the orientaion of the relativePos from

    Quaternion.FromToRotation (Vector3.up, -relativePos).

    to Quaternion.FromToRotation (Vector3.up, relativePos).

    and then try moving in a straight line, the piece will move in a circular motion toward the top of the sphere and not the bottom.
     
  12. pete

    pete

    Joined:
    Jul 21, 2005
    Posts:
    1,647
    i'll look at your script further but i think it's a more complex prob. after a quick skim...

    i wouldn't do all that in FixedUpdate and you're declaring ang and trans twice.

    transform.Rotate(0,angle,0);

    your x and z won't always be zero. you've got to set them relative to your last rotation.

    flipping between + or - relativePos is probably just giving you the results of your script. makes sense that it would give opposite results.

    the raycast thing probably didn't work because i just whipped it out between work without testing. i'll try to take a look again when i have a minute.
     
  13. joe gamble

    joe gamble

    Joined:
    Jan 3, 2007
    Posts:
    85
    yeah I already changed the rotate script to this:

    transform.Rotate(Vector3.up * angle);
     
  14. pete

    pete

    Joined:
    Jul 21, 2005
    Posts:
    1,647
    joe, this is working pretty well north of the equator. it doesn't work in the southern hemishpere yet. i'm still working on that.

    things like scale, mass, speed and drag will affect how the object sticks to the sphere. you'll have to play with them a little. using a 1 meter cube and a sphere primative scaled to 20, i had speed = 0.5, gravity = 50, mass = 0.2 and drag = 60. at these settings the cube will come off the surface when going fast enough but won't break gravity too easily.

    Code (csharp):
    1.  
    2. var speed = 0.5;
    3. var gravity = 50.0;
    4.  
    5. var surfaceNormal : Vector3;
    6. var hit : RaycastHit;
    7.  
    8. // assign the sphere as your target in the Inspector
    9. var target : Transform;
    10.  
    11.     function Update ()
    12.     {
    13.         // rotate our object to align up with the target's normal
    14.         if (Physics.Raycast (transform.position, Vector3.down, hit))
    15.         {
    16.             surfaceNormal = hit.normal;
    17.         }
    18.         transform.rotation = Quaternion.FromToRotation(Vector3.up, surfaceNormal);
    19.  
    20.         // I need to figure out how adjust angles south of the equator.
    21.         // Vector3.up is zero. So x and z both rotate 0-90 and 359-270.
    22.         // At this point it won't point down.
    23.     }
    24.  
    25.     function FixedUpdate()
    26.     {
    27.         // move our object
    28.         moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
    29.         moveDirection = transform.TransformDirection(moveDirection);
    30.         moveDirection *= speed;
    31.         transform.Translate (moveDirection, Space.World);
    32.  
    33.         // Apply gravity
    34.         rigidbody.AddForce((target.transform.position - transform.position).normalized * gravity);
    35.  
    36.         // When going fast enough the object will want to leave the surface due to inertia.
    37.         // Playing with mass, speed and gravity helps but it doesn't quite get there.
    38.         // Still trying to come up with a way to stick to the surface when moving.
    39.     }
    40.  
    41.     function Start ()
    42.     {
    43.         // Make the rigid body not change rotation
    44.         if (rigidbody)
    45.         {
    46.             rigidbody.freezeRotation = true;
    47.         }
    48.     }
    edit: oops... i had the sphere scaled to 20 not 10...
     
  15. joe gamble

    joe gamble

    Joined:
    Jan 3, 2007
    Posts:
    85
    ok I think I mostly have it working now. this version seems to work everywhere. just thought I would pass this along. this adjusts the distance so that if you have terrain on on the sphere the object will conform to the height of the terrain. Also this version does not use gravity or addForce to achieve the
    effect.

    ----------------------

    var target : Transform;
    var rayDistance = 250;
    var hoverDistance = 0.4;

    var forwardSpeed = 5;
    var rotationSpeed = 20.0;

    function Start ()
    {
    // Make the rigid body not change rotation
    if (rigidbody)
    {
    rigidbody.freezeRotation = true;
    rigidbody.isKinematic = true;
    }

    // set initial orientation
    var relativePos = target.position - transform.position;
    transform.rotation = Quaternion.FromToRotation(Vector3.up, -relativePos);

    fitToSphere();
    }

    function LateUpdate ()
    {
    if (target)
    {
    var y = Input.GetAxis ("Horizontal") * rotationSpeed * 0.02;
    var z = Input.GetAxis ("Vertical") * forwardSpeed * 0.02;

    // get the rotation, this correlates to the longitude lines

    var rotation = Quaternion.EulerAngles(0,y * Mathf.Deg2Rad,z * Mathf.Deg2Rad);

    // now add in the rotation that will line the model up with the right longitude line
    transform.rotation *= rotation;

    if(z != 0)
    {
    fitToSphere();
    }
    }
    }

    function fitToSphere()
    {
    // set the ray origin some where high over the sphere
    var rayOrigin = transform.rotation * Vector3(0.0,rayDistance,0.0) + target.position;

    // get the orintation for the ray, this should be pointing to the origin of the target object
    var down = transform.TransformDirection (Vector3.down);

    // cast a ray and get back everything it collided with
    var hits : RaycastHit[] = Physics.RaycastAll(rayOrigin, down, rayDistance);

    // set the distance equal to the origin distance
    var distanceToCollider = rayDistance;

    // go through all the colliders and try and find the one with highest collider
    for (var i=0;i<hits.length;i++)
    {
    var hit : RaycastHit = hits;

    // make sure the collider we are hitting isn't the one for this object
    if((collider == void) || (hit.collider != collider))
    {
    //check for the renderer, for some reason the code doesn't work right without the check
    var renderer = hit.collider.renderer;
    if (renderer)
    {
    // look for the collider with the least distance from the ray origin point
    if(hit.distance < distanceToCollider)
    {
    // set the distance down to the surface
    distanceToCollider = hit.distance;
    }
    }
    }
    }

    // reset the oject position so it's just above the collider
    transform.position = transform.rotation * Vector3(0.0,rayDistance - distanceToCollider + hoverDistance,0.0) + target.position;
    }
     
  16. pete

    pete

    Joined:
    Jul 21, 2005
    Posts:
    1,647
    cool joe. thanks for sharing it!
     
  17. sdgd

    sdgd

    Joined:
    Jan 15, 2013
    Posts:
    81
    no Quaternion.FromToRotation gives you south pole madness :) as it is pretty much variable on the position of the planet you are and every time you'll be on same spot you will be orientated same witch for sphere is impossible if you want to be orientated normally.

    1. if you try imagining aline the arrow around the sphere and you move it around X axis yes everything is great right?
    2. you move the arrow around the Z axis wow you see it crosses the path you went before with different angle.

    witch points out the conclusion you need to angle it correctly so if you went on same spot 2 times you might not be rotated same

    I was trying with this but problem started when I got more than 90° down I still don't understand why, ...

    FAILURE experiment:

    Code (csharp):
    1.  
    2.         Vector3 DirectionToPlanet = (Planet.position - transform.position).normalized;
    3.         Vector3 Temp = DirectionToPlanet - transform.up;
    4.         Vector3 TempLocal = -transform.up;
    5.         float AngleX = MF.V2Angle(new Vector2(DirectionToPlanet.x, DirectionToPlanet.y), new Vector2(TempLocal.x, TempLocal.y) );
    6.         float AngleZ = MF.V2Angle(new Vector2(DirectionToPlanet.z, DirectionToPlanet.y), new Vector2(TempLocal.z, TempLocal.y) );
    7.        
    8.         Debug.DrawRay(transform.position, DirectionToPlanet, Color.red, 5f);
    9.         Debug.DrawRay(transform.position, TempLocal, Color.green, 5f);
    10.        
    11.         Debug.Log("X: " + AngleX);
    12.         Debug.Log("Z: " + AngleZ);
    13.        
    14.         float devide = 10;
    15.        
    16.         if (AngleX > 180){
    17.             AngleX = 360 - (360 - AngleX)/ devide;
    18.         }
    19.         else {
    20.             AngleX /= devide;
    21.         }
    22.         if (AngleZ > 180){
    23.             AngleZ = 360 - (360 - AngleZ)/ devide;
    24.         }
    25.         else {
    26.             AngleZ /= devide;
    27.         }
    28.        
    29.         Temp.x = - AngleZ;
    30.         Temp.y = 0;
    31.         Temp.z = AngleX;
    32.         // Closest example to work don't understand why some stuff don't work
    33.         transform.localEulerAngles += Temp;
    34.  
    than I found this article and I went looking how he did it:
    http://answers.unity3d.com/questions/155907/basic-movement-walking-on-walls.html

    AND WOA IT WORKS!!!

    Code (csharp):
    1.  
    2.     RaycastHit hit;
    3.     if (Physics.Raycast(transform.position, - transform.up, out hit) ){
    4.         if (hit.collider.transform == Planet.transform){
    5.             Vector3 MyForward = Vector3.Cross(transform.right, hit.normal);
    6.             transform.rotation = Quaternion.LookRotation(MyForward, hit.normal);
    7.         }
    8.     }
    9.