Search code examples
pythonmatrixrotationrotational-matricesursina

How do you rotate ursina entities around their local axes?


I can’t get my code for rotating a 3D object around its local axes to work correctly. I’m using the Ursina game engine. The 3D objects that I want to rotate extend the Entity class, which has a rotation property that’s Euler angles. I learned via testing that Ursina does Euler rotations in the Z,X,Y order—correct me if I’m wrong. I didn’t find the Euler order in Ursina’s documentation.

import numpy as np
from scipy.spatial.transform import Rotation as R
from ursina import *

class FreeRotateEntity(Entity):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def rotate(self, angles):
        self.rotation = Vec3(*[360 + a if a < 0 else a for a in [
            R.from_matrix(
                np.matmul(
                    R.from_euler('zxy', (self.rotation.z, self.rotation.x, self.rotation.y), degrees=True).as_matrix(),
                    R.from_euler('zxy', [angles[i] for i in (2, 0, 1)], degrees=True).as_matrix()
                )
            ).as_euler('zxy', degrees=True)[i] for i in (1, 2, 0)
        ]])

class Ship(FreeRotateEntity):
    …

The code multiplies the object’s current rotation matrix with the matrix of the new rotation (around local, not global, axes) to be applied. It doesn’t rotate properly.

I tried swapping the order of the matrix multiplication, changing the Euler orders, and using scipy.spatial.transform.Rotation.apply instead of matrix multiplication, but none of these worked.

What did I do wrong in the rotate method?


Solution

  • The default rotation axes in ursina are as follows:

    • x: rotate clockwise around the x-axis, as seen from the outside in.
    • y: rotate clockwise around the y-axis, as seen from the outside in.
    • z: rotate counter clockwise around the z-axis, as seen from the outside in. This is opposite because of 2d-games :|

    Euler angles are kind of limited in some situations. To get/set the angle as quaternion use entity.quat.

    It would be nice if you could explain the your actual problem is.

    If you goal is to rotates axes individually, consider using a hierarchy of entities. This is how a first person or third person shooter would work. You rotate the player on y so it's always standing, but rotate the camera up and down separately:

    from ursina import *
    
    app = Ursina()
    
    cube_parent = Entity()
    cube = Entity(parent=cube_parent, model='cube', texture='white_cube')
    
    def update():
        cube_parent.rotation_y += 100 * (held_keys['a'] - held_keys['d']) * time.dt
        cube_parent.rotation_x += 100 * (held_keys['w'] - held_keys['s']) * time.dt
    
    app.run()
    

    If you want to rotate something continually, euler angles is not enough, since you'll run in to gimbal lock. Consider using a trick like this, which will rotate the entity like a ball.

    from ursina import *
    
    app = Ursina()
    
    rotation_resetter = Entity()
    cube = Entity(parent=rotation_resetter, model='cube', texture='white_cube')
    
    
    def update():
        rotation_resetter.rotation_x += 100 * (held_keys['a'] - held_keys['d']) * time.dt
        rotation_resetter.rotation_z += 100 * (held_keys['w'] - held_keys['s']) * time.dt
    
        cube.rotation = cube.world_rotation
        rotation_resetter.rotation = (0,0,0)
    
    EditorCamera()
    
    app.run()
    

    Another option for making working with rotation easier is using entity.look_at()