How to rotate around localAxis?

I am really bad at math(s), and just had a unsuccessful day trying to recreate the transform.RotateAround functionality in ECS. Through my research I found this thread , which includes the function for the translation; but I couldn’t figure out how to make it rotate (for my needs) as well.

What I want is:

  • player can rotate about local y axis using mouse
  • players rotation stays “in sync” with position around sphere

Maybe this gif can better illustrate:
InsignificantMindlessBluegill
In this case, the rotation veers “off course” (while just holding down one button, no mouse input) as soon as you start moving in any second axis (here is image of the path in a separate run).
I suspect the solution will be embarrassingly easy, just my poor brain could not get with it ;(. It’s the end of the day for me, thank you all in advanced; here is the code I left off with:

 protected override unsafe void OnUpdate()
        {
            Entities.ForEach(
                (ref PlayerController playerController,
                ref LocalToWorld toWorld,
                ref Translation position,
                ref Rotation rotation) =>
                {
                    float deltaTime = Time.deltaTime;

                    float velX = Input.GetAxisRaw("Horizontal");
                    float velZ = Input.GetAxisRaw("Vertical");

                    float rotY = Input.GetAxis("Mouse X");
                    bool changedRot = false;
                    Quaternion quat = rotation.Value;
                    if (math.abs(rotY) > float.Epsilon)
                    {
                        float delta = rotY * playerController.RotateSpeed * deltaTime;
                        quat = quat * quaternion.AxisAngle(toWorld.Up, delta);
                        changedRot = true;
                        Debug.DrawRay(position.Value, toWorld.Up, Color.green, 255);
                    }

                    if (math.abs(velX) > float.Epsilon)
                    {
                        float delta = -velX * playerController.MoveSpeed * deltaTime;
                        Quaternion rot = quaternion.AxisAngle(toWorld.Forward, delta);
                        Debug.DrawRay(position.Value, toWorld.Forward, Color.red, 255);
                        position.Value = rot * position.Value;
                        quat = quat * rot;
                        changedRot = true;
                    }

                    if (math.abs(velZ) > float.Epsilon)
                    {
                        float delta = velZ * playerController.MoveSpeed * deltaTime;
                        Quaternion rot = quaternion.AxisAngle(toWorld.Right, delta);
                        position.Value = rot * position.Value;
                        quat = quat * rot;
                        changedRot = true;
                        Debug.DrawRay(position.Value, toWorld.Right, Color.blue, 255);

                    }
                    if (changedRot)
                        rotation.Value = quat;
                });
        }
    }

First of all you should do all that in a Bursted Job so you can take advantage of multi-threading and burst compiler.
This is what i use to rotate around a center point in a certain axis.

  public static float4x4 RotateAround(LocalToWorld localToWorld, float3 center, float3 axis, float angle) {
    var initialRot = quaternion.LookRotationSafe(localToWorld.Forward, localToWorld.Up);
    var rotAmount = quaternion.AxisAngle(axis, angle);
    var finalPos = center + math.mul(rotAmount, localToWorld.Position - center);
    var finalRot = math.mul(math.mul(initialRot, math.mul(math.inverse(initialRot), rotAmount)), initialRot);
    return new float4x4(finalRot, finalPos);
  }

In my case use mouse input to rotate the camera around another object.
Hope this helps you going forward.

1 Like

Hi GilCat,
In order to rotate this in two dimensions, how do you get the float3 axis to rotate about? Or do you call this function twice in in separate axis?

I’m still not sure how to rotate about “local y axis” while keeping orientation with the sphere.

Also I am unsure if this functionality deserves to be put in a job, since it will only be applying to one entity I think it would be overkill. I will probably use PostUpdateCommands once I get it working.

Anyways, I’ll be sure to keep your function in mind while experimenting today, hopefully more luck this morning.
Thanks :slight_smile:

Actually you got me interested in:
var finalRot = math.mul(math.mul(initialRot, math.mul(math.inverse(initialRot), rotAmount)), initialRot);
Perhaps this is the key, could you explain how it works or maybe point to where I can read more about it?
Unfortunately I’m really bad a math so I can’t figure it out haha.
It’s got me interested since I’m just used to seeing (using) something like:

You can read about quaternion rotation here.
You can also look in here where there is some implementation of it using legacy unity math API.

Ahh nevermind… that’s exactly what I needed! Thank you very much GilCat it works perfect :). If you dont mind I’ve got a couple of questions I would like to ask:

  • Is there a way to directly convert a float4 to a float3, right now I am using a Matrix4x4 and a Vector3 to convert so I assume it must be possible?
  • Do you know a way to get up, forward, right etc… directions from a float4x4 representing rotation and translation? Right now I am using a “temp” LocalToWorld.Value

Here is working code for anyone who wants it in future, probably will need fixing:

public class RotateCharacterController : ComponentSystem
    {
        protected override unsafe void OnUpdate()
        {
            Entities.ForEach(
                (
                ref PlayerController playerController,
                ref LocalToWorld toWorld,
                ref Translation position,
                ref Rotation rotation) =>
                {
                    float deltaTime = Time.deltaTime;

                    float velX = Input.GetAxisRaw("Horizontal");
                    float velZ = Input.GetAxisRaw("Vertical");

                    float rotY = Input.GetAxis("Mouse X");
                    LocalToWorld tempWorld = new LocalToWorld { Value = toWorld.Value};
                    Quaternion tempRot = rotation.Value;
                    float3 tempPos = position.Value;
                    bool rotated = false;
                    bool translated = false;
                    if (math.abs(rotY) > float.Epsilon)
                    {
                        rotated = true;
                        float delta1 = math.mul(math.mul(rotY,playerController.RotateSpeed),  deltaTime);
                        tempRot = math.mul(tempRot, quaternion.AxisAngle(math.up(), delta1));
                        tempWorld.Value = new float4x4(tempRot, position.Value);
                        //Debug.DrawRay(position.Value, math.up(), Color.green, 255);
                    }

                    if (math.abs(velX) > float.Epsilon)
                    {
                        rotated = true;
                        translated = true;
                        float delta = math.mul(math.mul(-velX, playerController.MoveSpeed), deltaTime);
                        float3 dir = tempWorld.Forward;
                        Matrix4x4 matrix = Common.RotationFunctions.RotateAround(tempWorld, float3.zero, dir, delta);
                        Vector3 pos = matrix.GetColumn(3);
                        tempWorld.Value = matrix;
                        tempPos = pos;
                        tempRot = Quaternion.LookRotation(matrix.GetColumn(2), matrix.GetColumn(1));
                    }
                    if (math.abs(velZ) > float.Epsilon)
                    {
                        rotated = true;
                        translated = true;
                        float delta = math.mul(math.mul(velZ, playerController.MoveSpeed),  deltaTime);
                        float3 dir = tempWorld.Right;
                        Matrix4x4 matrix = Common.RotationFunctions.RotateAround(tempWorld, float3.zero, dir, delta);
                        Vector3 pos = matrix.GetColumn(3);
                        tempWorld.Value = matrix;
                        tempPos = pos;
                        tempRot = Quaternion.LookRotation(matrix.GetColumn(2), matrix.GetColumn(1));
                    }
                    if (rotated)
                    {
                        //Rotation rot = new Rotation { Value = tempRot };
                        //PostUpdateCommands.SetComponent<Rotation>(entity, rot);
                        rotation.Value = tempRot;
                        if (translated)
                        {
                            //Translation pos = new Translation { Value = tempPos };
                            //PostUpdateCommands.SetComponent<Translation>(entity, pos);
                            position.Value = tempPos;
                        }
                    }
                });
        }
    }

No problem. Glad I could help.
About your questions:

  1. Yes. float4 has a xyz property that gives you a float3.
  2. LocalToWorld has a Position, Forward and Up properties but you can also get that from float4x4.
    In float4x4, c0 is Right vector, c1 is Up vector, c2 is Forward vector and c3 is Position.

Sorry for any typo as I’m on the phone right now.

1 Like

You should use JobComponentSystem instead alongside with bursted jobs.
The way i would do it would be to have a system to collect player input and another to process that input.
In DOD concerns should be separated as much as possible.

1 Like

When I call RotateAround method what should the argument for LocalToWorld variable be?
In other words how to I convert float4x4 to float3?

Edit - Got it…working.