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?
The default rotation axes in ursina are as follows:
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()