Rotate a model around its own axes

16,343

Solution 1

Ok, spoke to a mathematician friend and he gave me the answer:

so i think what you need to do if you're rotating around the vector (1,0,0) by an angle of 'a' (i.e rotating around the x-axis so transforming your object in the y-z plane).

Further rotations are around

x' - (1,0,0) stays the same!

y' - (0,cosa,sina)

z' - (0,-sina,cosa)

a similar principle will hold for rotations in the x-z plane (0,1,0)

x' - (-sina,0, cosa)

y' - (0,1,0) - the same

z' - (sina,o,cosa)

and in the x-y plane around (0,0,1)

x' - (-sina,cosa,0)

y' - (cosa,sina,0)

z' - (0,0,1) stays the same

TADA!

UPDATE: I created a function to calculate a matrix which will rotate an object in all 3 axes. This can be used with a MatrixTransform3D.

    Matrix3D CalculateRotationMatrix(double x, double y, double z)
    {
        Matrix3D matrix = new Matrix3D();

        matrix.Rotate(new Quaternion(new Vector3D(1, 0, 0), x));
        matrix.Rotate(new Quaternion(new Vector3D(0, 1, 0) * matrix, y));
        matrix.Rotate(new Quaternion(new Vector3D(0, 0, 1) * matrix, z));

        return matrix;
    }

Solution 2

The answer you came up with composes three different rotations, providing a single transform matrix that you can apply to the object. This works, but given the wording of the question, it's unnecessary.

In the question as described, you wanted the plane rotated about its Z axis, but then the result of that rotation rotated about the scene's X axis, like so:

after two rotations

That's the result of adding the transform you came up with to the original one, i.e. with a grouped transform like this:

<Transform3DGroup>
  <RotateTransform3D>
    <RotateTransform3D.Rotation>
      <AxisAngleRotation3D Axis="1,0,0" Angle="-45"/>
    </RotateTransform3D.Rotation>
  </RotateTransform3D>
  <RotateTransform3D>
    <RotateTransform3D.Rotation>
      <AxisAngleRotation3D Axis="0,1,1" Angle="45"/>
    </RotateTransform3D.Rotation>
  </RotateTransform3D>
</Transform3DGroup>

The thing is, the only problem you actually had in the first place, at least given the problem as stated, was that you composed your original transforms in the wrong order. You can accomplish the exact same result simply by inserting the Z-axis rotation before the X-axis rotation instead of after:

<Transform3DGroup>
  <RotateTransform3D>
    <RotateTransform3D.Rotation>
      <AxisAngleRotation3D Axis="0,0,1" Angle="45"/>
    </RotateTransform3D.Rotation>
  </RotateTransform3D>
  <RotateTransform3D>
    <RotateTransform3D.Rotation>
      <AxisAngleRotation3D Axis="1,0,0" Angle="-45"/>
    </RotateTransform3D.Rotation>
  </RotateTransform3D>
</Transform3DGroup>

Indeed, the method you posted in your posted answer is really just a different way of accomplishing the same composition. If you compare the Matrix3D value returned from a call to your method, passing -45 and 45, i.e. CalculateRotationMatrix(-45, 0, 45), you get essentially the same value returned by the Transform3DGroup.Value property for a group declared (or initialized in code-behind) as above.

The only reason it seems like a different approach is that the order of composition is different when dealing with the matrix math directly (as your method does) as compared to using the Transform3DGroup object to do the composition (because that object reverses the composition so that it appears more naturally in e.g. a XAML declaration).

(I say "essentially", because there is a very tiny difference in some of the matrix components due to the rounding error introduced by the extra step of computing the quaternions. E.g. when grouping the rotations, the M12 component has value 0.5, while the same component using your method has value 0.49999999999999989. The matrixes won't compare as identical, but they have the exact same visual effect when applied to the object in the scene.)

Now, there's nothing wrong at all with computing a matrix in code-behind, if that's the way your code needs to be architected. And using quaternions is a tried and true approach, especially for the purpose of interpolating between two different rotations (i.e. you want to animate the rotation and easily compute intermediate rotations for the purpose).

But I think it's important to understand that there's not really any such thing as "rotating an object about its own axes". The transformation of an object always happens relative to the coordinate space it lives in, not its own local coordinate space. The latter is always moved to remain in alignment with the object itself, any time that object is rotated or otherwise transformed relative to its parent (whether that's the whole scene, as for a top-level object, or some other object within the scene that the first object is a child of).

There are many different ways in WPF to compose 3D transform matrixes, but they all really come down to the same thing: multiplying two or more matrixes to arrive at a final one. This is even true when the transforms are applied to different objects via parent/child relationships in the scene's graph of objects. In fact, it's IMHO more idiomatic in a 3D rendering API to use hierarchical relationships of objects if you intend for a given object to only rotate about a single axis of its own. I.e. in your example, you could have given your object a parent object, to which the X-axis rotation is applied, and then applied just the Z-axis rotation to the child object.

E.g. something like this:

<!-- Parent 3D visual -->
<ModelVisual3D>
  <ModelVisual3D.Transform>
    <RotateTransform3D>
      <RotateTransform3D.Rotation>
        <AxisAngleRotation3D Axis="1,0,0" Angle="-45"/>
      </RotateTransform3D.Rotation>
    </RotateTransform3D>
  </ModelVisual3D.Transform>
      
  <!-- Child 3D visual -->
  <ModelVisual3D>
    <ModelVisual3D.Content>
      <GeometryModel3D>
        <GeometryModel3D.Geometry>
          <MeshGeometry3D Positions="-1,-1,0  1,-1,0  -1,1,0  1,1,0"
                                TriangleIndices="0,1,2 1,3,2"/>
        </GeometryModel3D.Geometry>
        <GeometryModel3D.Material>
          <DiffuseMaterial Brush="Red"/>
        </GeometryModel3D.Material>
      </GeometryModel3D>
    </ModelVisual3D.Content>

    <ModelVisual3D.Transform>
      <RotateTransform3D>
        <RotateTransform3D.Rotation>
          <AxisAngleRotation3D Axis="0,0,1" Angle="45"/>
        </RotateTransform3D.Rotation>
      </RotateTransform3D>
    </ModelVisual3D.Transform>
  </ModelVisual3D>
</ModelVisual3D>

Again, same exact result, just a different way of expressing the composition of the matrixes. In this case, this would be a more natural way of expressing the transforms in a scenario where the rendered plane is only supposed to rotate about "its own" Z-axis, but still move and/or rotate relative to some parent that also moves and/or rotates about in the scene (for example, the hand on an articulated robotic arm, the wheel on a car, or a turret on some combat vehicle, to name a few examples that often show up in 3D-rendered scenes).

Share:
16,343
Grokys
Author by

Grokys

Updated on June 22, 2022

Comments

  • Grokys
    Grokys almost 2 years

    Suppose I have a simple WPF 3D scene set up with a single rectangle rotated -45 degrees around the X axis like so:

    <Viewport3D>
        <Viewport3D.Camera>
            <PerspectiveCamera Position="0,0,4"/>
        </Viewport3D.Camera>
        <ModelVisual3D>
            <ModelVisual3D.Content>
                <DirectionalLight Color="White" Direction="-1,-1,-3" />
            </ModelVisual3D.Content>
        </ModelVisual3D>
        <ModelVisual3D>
            <ModelVisual3D.Content>
                <GeometryModel3D>
                    <GeometryModel3D.Geometry>
                        <MeshGeometry3D Positions="-1,-1,0  1,-1,0  -1,1,0  1,1,0"
                                        TriangleIndices="0,1,2 1,3,2"/>
                    </GeometryModel3D.Geometry>
                    <GeometryModel3D.Material>
                        <DiffuseMaterial Brush="Red"/>
                    </GeometryModel3D.Material>
                </GeometryModel3D>
            </ModelVisual3D.Content>
            <ModelVisual3D.Transform>
                <Transform3DGroup>
                    <RotateTransform3D>
                        <RotateTransform3D.Rotation>
                            <AxisAngleRotation3D Axis="1,0,0" Angle="-45"/>
                        </RotateTransform3D.Rotation>
                    </RotateTransform3D>
                </Transform3DGroup>
            </ModelVisual3D.Transform>
        </ModelVisual3D>
    </Viewport3D>
    

    This gives me the following:

    rotated about X axis

    Now I want to rotate the image 45 degrees around the model's Z axis. If I just put a second RotateTransform3D in like so:

    <RotateTransform3D>
        <RotateTransform3D.Rotation>
            <AxisAngleRotation3D Axis="0,0,1" Angle="45"/>
        </RotateTransform3D.Rotation>
    </RotateTransform3D>
    

    It rotates around the scene's Z axis. For this particular X rotation I've worked out what I need is:

    <RotateTransform3D>
        <RotateTransform3D.Rotation>
            <AxisAngleRotation3D Axis="0,1,1" Angle="45"/>
        </RotateTransform3D.Rotation>
    </RotateTransform3D>
    

    But here my maths fails me. Could anyone tell me how to work this out for an arbitrary rotation "A" (and "B" if you would like to)?