Southclaws
Southclaws

Reputation: 1940

Matrix rotate a bunch of vectors about a point in Python

I've been trying all morning to figure this problem out, eventually had to resort to SO.

I'm trying to rotate a set of 'objects' which have a 3D position and rotation (it's actually for another program but I am writing a quick Python tool to parse the data, rotate it how I want and spit it back out).

In my program there are two classes:

class Object:

    def __init__(self, mod, px, py, pz, rx, ry, rz):
        self.mod = mod
        self.pos = [px, py, pz]
        self.rot = [rx, ry, rz]


    def rotate(self, axisx, axisy, axisz, rotx, roty, rotz):
        """rotates object around axis"""

        ?

This is my 'object' class (okay, I realise now how badly that's named!). Ignore 'mod', it's very simple, just exists in space with a position and rotation (degrees).

I have no idea what to write into the rotate part. I sort of get matrices but only in the mathematical form, I've never actually written code for them and wondered if there are any libraries out there to help.

My other class is a simple group for these objects. The only other attribute is an averaged position which is actually the axis that I want to rotate each of the objects around:

class ObjectMap:

    def __init__(self, objs):
        self.objs = objs

        tpx = 0.0
        tpy = 0.0
        tpz = 0.0

        for obj in objs:
            tpx += obj.pos[0]
            tpy += obj.pos[1]
            tpz += obj.pos[2]

        # calculate average position for this object map
        # somewhere in the middle of all the objects
        self.apx = tpx / len(objs)
        self.apy = tpy / len(objs)
        self.apz = tpz / len(objs)



    def rotate(self, rotx, roty, rotz):
        """rotate the entire object map around the averaged position in the centre"""

        for o in self.objs:
            o.rotate(self.apx, self.apy, self.apz, rotx, roty, rotz)

As you can see, there is a rotate function for this class which simply runs through all the objects contained within it and rotates them about the "average position" axis which should be somewhere in the middle since it's an average.

I made a quick animation to better explain what I am after here:

http://puu.sh/i3DxU/adfe44a99d.gif http://puu.sh/i3DxU/adfe44a99d.gif Where the sphere shapes are my "objects" and the shape in the middle is the axis they are rotating around (the apx, apy, apz coordinates of the ObjectMap class).

I tried to get this library working but it just wasn't working so I abandoned that idea. I'm using Python 3, got numpy installed as I figured it would help. I've also tried numerous bits of code on the internet but things just aren't working (or they are for old python versions, or just plain fail at installing).

I'd love if someone could point me in the right direction for getting these rotations working. Even just a link to an example of matrices in Python or a useful library would be great!

Upvotes: 0

Views: 3748

Answers (1)

David K
David K

Reputation: 3132

Edit: My original answer avoided pitch, roll, and yaw entirely. Based on a clarification of the question, it seems this code may be using data structures and/or APIs that require the use of pitch, roll, and yaw, so I will now try to address this requirement.

There are several ways to specify a rotation in a three-dimensional Cartesian coordinate system:

  • Euler angles (3 numeric parameters)
  • Axis and angle (4 numeric parameters)
  • Rotation matrix (9 numeric parameters)
  • Quaternions (4 numeric parameters)

Yaw, pitch, and roll are Euler angles (at least according to any applicable definitions I know of those three terms). But transformations.py says there are 24 possible ways to interpret a sequence of three Euler angles, and every single one of those interpretations has different results from each of the others for at least some sequences of angles. It's not obvious how to translate "yaw, pitch, and roll" to one of the 24 possible "axis sequences" in transformations.py. In fact, unless you know exactly how the existing data/software you are interfacing with applies yaw, pitch, and roll to objects that are to be rotated, I don't think you can really say what "yaw, pitch, and roll" means in this application, and you are unlikely to guess the correct "axis sequence" to use in transformations.py. I suspect this may be the main reason why you have not been able to get transformations.py to work for you.

On top of all that ambiguity, it's unclear to me what the parameters axisx, axisy, and axisz represent in rotate(self, axisx, axisy, axisz, rotx, roty, rotz). Conventionally, yaw, pitch, and roll refer to rotations about three axes of the body being rotated, and generally one is supposed to define what those axes are and the order in which the rotations are applied before doing any rotations, and never change those definitions. So it really makes no sense to specify axes every time one has to do another rotation; the software should already know exactly which axes to use, even if they are body axes rather than world axes. (I'm assuming now that each of the parameters axisx, axisy, and axisz is an axis by itself, and that these three parameters are not somehow being used to specify a single axis as I assumed in my initial answer.)

To add to the confusion, while pitch, roll, and yaw are typically applied to body axes, you are supposed to be rotating an entire ensemble of objects, which seems to imply you should be rotating around world axes rather than individual body axes.

In practical terms, once you figure out what yaw, pitch, and roll really mean in your application, and what the parameters of your rotate function are supposed to mean, the first thing I would do with any rotation is to convert it to a representation that is not any kind of Euler angles. Rotation matrices look like a good choice. If you know the correct "axis sequence" that represents your definition of yaw, pitch and roll in transformations.py, euler_matrix promises to compute that matrix for you.

You can further rotate objects by doing a matrix multiplication of the new rotation matrix and the matrix of the existing rotation; the result is a third matrix. The new matrix goes on the left in the multiplication if it is a rotation in world coordinates, on the right if it is a rotation in body coordinates.

Once you have reoriented your objects using the new rotation matrix, if you really need to store the resulting orientation of the object as a sequence of Euler angles (roll, pitch, and yaw) somewhere, euler_from_matrix in transformations.py promises to tell you what those angles are (but once again, you have to know how your "roll, pitch, and yaw" are defined and how transformations.py represents that definition as an axis sequence).

Below the line is material from my original answer (that is, thoughts about how one might do things if one were not forced to use Euler angles).


My recommendations:

For Object, the rotation function signature should be something equivalent to:

    def rotate(self, axisx, axisy, axisz, angle)

For ObjectMap the signature should be equivalent to

    def rotate(self, angle)

The idea is that once you choose an axis (either through input variables to the function or implicitly already computed as in ObjectMap), the only difference between any two rotations is the angle of rotation around that axis, described by a single scalar parameter angle. (I recommend that the units of angle be radians.)

To relate this to your GIF, in the GIF each of the colored arcs has its own axis perpendicular to the plane of the arc. The radius of the arc does not really matter; the only other thing that controls how the spheres move (when you rotate around that axis) is how far the pointer has moved back or forth along the arc. Motion back or forth is something that is described by a single scalar parameter, in this case the angle of rotation.

For the contents of Object, it takes one set of coordinates to specify the location of the object's "position" (which could actually be any reference point of your choosing on the object; but the center is usually a good choice if there's an obvious center):

    def __init__(self, mod, px, py, pz):
        self.mod = mod
        self.position = [px, py, pz]

If you also want to represent the orientation of the object (for example, so that the spheres in your GIF appear to rotate around their own axes as they revolve around the axis of the ObjectMap), add sufficient points to the Object (in addition to the "position") so that you can draw it wherever it may end up after rotation. The minimum is two additional points; for example, to draw a sphere like one of the ones in your GIF, it is sufficient to know the location of the north pole and the location of one point on the equator. (It is actually possible to know the exact orientation of the sphere with only three scalar coordinates, rather than the six involved in these two points, but I would not recommend it unless you're willing to seriously study the mathematics involved.)

This leads to something like this:

    def __init__(self, mod, px, py, pz, vector1x, vector1y, vector1z, vector2x, vector2y, vector2z):
        self.mod = mod
        self.position = [px, py, pz]
        self.feature1 = [px + vector1x, py + vector1y, pz + vector1z]
        self.feature2 = [px + vector2x, py + vector2y, pz + vector2z]

The rationale for px + vector1x, etc., is that you might find it convenient to describe the features of your objects by vectors from the object's center to each feature; but for the drawing and rotation of objects you may prefer for all the points to be described by their global coordinates. (I should note, however, it is not necessary to describe the object entirely in global coordinates.)

The rotation of an Object then becomes something like this pseudocode:

    def rotate(self, axisx, axisy, axisz, angle):
        rotate self.position around [axisx, axisy, axisz] by angle radians
        rotate self.feature1 around [axisx, axisy, axisz] by angle radians
        rotate self.feature2 around [axisx, axisy, axisz] by angle radians

If all your coordinates are global coordinates, this will move all of them around the global axis, resulting in movement and rotation of the objects made of those points. If you decide that only the center of an Object has a global coordinate and all the other parts of it are described relative to that point, using vectors, you can still use the same rotate function; the result will be the same as if you simply rotated the global coordinates of each point.

The actual details of how to rotate a point in 3D space around a given axis by a given angle are written up in various places such as Python - Rotation of 3D vector (which includes at least one numpy implementation). You may find it beneficial to create a "rotation" object (probably a matrix, but you can let the library take care of the details of setting its values) via something like

    rotation = define_rotation(axisx, axisy, axisz, angle)

so that you can repeatedly use this same object to compute the rotated position of every point within every Object. This tends to yield faster execution than if you have to compute every rotation of every point from the original axis coordinates and angle value.

If this were my code I'd rather define a Point class and/or a Vector class (or use classes from an existing library) consisting of x, y, and z coordinates of a single point or vector, so that I would not have to keep passing parameters in groups of three and writing out vector-addition formulas throughout my code. For example, instead of

    self.feature1 = [px + vector1x, py + vector1y, pz + vector1z]

I might have

    self.feature1 = p.add(vector1)

But that is a design choice that you can make independently of what math you ultimately choose to do for the rotations.

Upvotes: 3

Related Questions