1

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?

clickbait
  • 2,818
  • 1
  • 25
  • 61

1 Answers1

2

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()

pokepetter
  • 1,383
  • 5
  • 8