0
$\begingroup$

One of things I need quite a bit is the vector of a local axis in the Y direction. Currently I'm doing this by calculating the vector using two known points along that axis:

    # Vector1 is along the wall edge from origin to vertex 3
    loc1 = wall.matrix_world @ wall.data.vertices[3].co 
    loc2 = wall.location
    v1 = Vector((loc1.x - loc2.x, loc1.y - loc2.y, 0))
    v1.normalize()

This works as long as I know which vertex to use since I created those as a mesh. With that vector I can move anything up and down that axis in the desired direction (in this example a wall), which of course is super handy. All of the walls are aligned this way so that I will always know the relative direction they are pointing.

However later I will be using objects without known vertices. Everything I've seen on how to calculate in those situations are by calculating angle differences from a known direction (such as up or forward, etc). This adds some unknowns and a bit of complexity.

The vectors along a local axis seems like something Blender would already know. Does such a thing exist so that it can be retrieved without calculating?

$\endgroup$
  • $\begingroup$ Just to be sure I'm not misunderstanding your question: You just want the local Y-axis as normalized vector? $\endgroup$ – Robert Gützkow Oct 22 '19 at 20:23
  • $\begingroup$ Yes @rjg that's correct. $\endgroup$ – Sam Vimes Oct 22 '19 at 20:29
2
$\begingroup$

The two essential information that you need are the world matrix and the axis / vector in local space that you're interested in. In your case the latter is pointing in the direction of the local y-axis which is equivalent to the vector:

$$axis_{local\ y} = \begin{pmatrix}0.0 \\ 1.0 \\ 0.0 \end{pmatrix}$$

The world matrix contains the information how the object is rotated in the global coordinate system. Therefore we need to decompose the world matrix to get the rotation matrix*, which can then be multiplied with the vector to get the direction of the local y-axis in the global coordinate system.

(*) Internally this is a quaternion

For example, if the object isn't rotated at all, the rotation matrix is:

$$rot = \begin{pmatrix}1.0 & 0.0 & 0.0 \\ 0.0 & 1.0 & 0.0 \\ 0.0 & 0.0 & 1.0 \end{pmatrix}$$

$$\begin{split}axis_{global\ y} &= rot \cdot axis_{local\ y}\\ &= \begin{pmatrix}1.0 \times 0.0 + 0.0 \times 1.0 + 0.0 \times 0.0 \\ 0.0 \times 0.0 + 1.0 \times 1.0 + 0.0 \times 0.0 \\ 0.0 \times 0.0 + 0.0 \times 1.0 + 0.0 \times 0.0 \end{pmatrix}\\ &= \begin{pmatrix}0.0 \\ 1.0 \\ 0.0 \end{pmatrix}\end{split}$$

The axis doesn't change, which is what we would expect since the local coordinate system is aligned with the global coordinate system. When the object is rotated by 90° around the global x-axis, the local y-axis now points upwards in the global coordinate system:

$$rot = \begin{pmatrix}1.0 & 0.0 & 0.0 \\ 0.0 & 0.0 & -1.0 \\ 0.0 & 1.0 & 0.0 \end{pmatrix}$$

$$\begin{split}axis_{global\ y} &= rot \cdot axis_{local\ y}\\ &= \begin{pmatrix}1.0 \times 0.0 + 0.0 \times 1.0 + 0.0 \times 0.0 \\ 0.0 \times 0.0 + 0.0 \times 1.0 - 1.0 \times 0.0 \\ 0.0 \times 0.0 + 1.0 \times 1.0 + 0.0 \times 0.0 \end{pmatrix}\\ &= \begin{pmatrix}0.0 \\ 0.0 \\ 1.0 \end{pmatrix}\end{split}$$

import bpy
import mathutils

obj = bpy.data.objects["Cube"] # Replace with your own object selection

(translation, rotation, scale) = obj.matrix_world.decompose()
local_y_axis = mathutils.Vector((0.0, 1.0, 0.0))
local_y_axis_global_coords = rotation @ local_y_axis 
print(local_y_axis_global_coords)

The approach works with any vector in local space, not just the axis.


If you're only interested in the $x$-, $y$- or $z$-axis, then you don't need to multiply with the rotation matrix as Robin Betts correctly noted. When the world matrix is normalized or the object scale is 1.0, then the local $x$-, $y$- and $z$-axis in global coordinates are equivalent to the first three column vectors.

import bpy
import mathutils

obj = bpy.data.objects["Cube"] # Replace with your own object selection

normalize_matrix_world = obj.matrix_world.normalized()

local_x_axis_global_coords = [row[0] for row in normalize_matrix_world[:3]]
local_y_axis_global_coords = [row[1] for row in normalize_matrix_world[:3]]
local_z_axis_global_coords = [row[2] for row in normalize_matrix_world[:3]]
print(local_x_axis_global_coords)
print(local_y_axis_global_coords)
print(local_z_axis_global_coords)

The world matrix is the result of multiplication of the scaling, rotation and translation matrices.

Scaling with matrix $S$ $$\begin{pmatrix}x'\\y'\\z'\\w'\end{pmatrix} = \begin{pmatrix}\alpha & 0 & 0 & 0 \\ 0 & \beta & 0 & 0 \\ 0 & 0 & \gamma & 0 \\ 0 & 0 & 0 & 1\end{pmatrix} \cdot \begin{pmatrix}x\\y\\z\\w\end{pmatrix}$$

Translation with matrix $T$ $$\begin{pmatrix}x'\\y'\\z'\\w'\end{pmatrix} = \begin{pmatrix} 1 & 0 & 0 & t_x \\ 0 & 1 & 0 & t_y \\ 0 & 0 & 1 & t_z \\ 0 & 0 & 0 & 1\end{pmatrix} \cdot \begin{pmatrix}x\\y\\z\\w\end{pmatrix}$$

Rotation around x-axis with $R_x$: $$\begin{pmatrix}x'\\y'\\z'\\w'\end{pmatrix} = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & cos(\theta) & -sin(\theta) & 0 \\ 0 & sin(\theta) & cos(\theta) & 0 \\ 0 & 0 & 0 & 1\end{pmatrix} \cdot \begin{pmatrix}x\\y\\z\\w\end{pmatrix}$$

Rotation around y-axis with $R_y$: $$\begin{pmatrix}x'\\y'\\z'\\w'\end{pmatrix} = \begin{pmatrix} cos(\theta) & 0 & sin(\theta) & 0 \\ 0 & 1 & 0 & 0 \\ -sin(\theta) & 0 & cos(\theta) & 0 \\ 0 & 0 & 0 & 1\end{pmatrix} \cdot \begin{pmatrix}x\\y\\z\\w\end{pmatrix}$$

Rotation around z-axis with $R_z$: $$\begin{pmatrix}x'\\y'\\z'\\w'\end{pmatrix} = \begin{pmatrix} cos(\theta) & -sin(\theta) & 0 & 0 \\ sin(\theta) & cos(\theta) & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1\end{pmatrix} \cdot \begin{pmatrix}x\\y\\z\\w\end{pmatrix}$$

The rotation matrix is: $$\begin{split}R &= R_x \cdot R_y \cdot R_z \\ &= \begin{pmatrix}cos(\theta_y) \cdot cos(\theta_z) & -cos(\theta_y) \cdot sin(\theta_z) & sin(\theta_y) & 0 \\ sin(\theta_x) \cdot \sin(\theta_y) \cdot cos(\theta_z) + cos(\theta_x) \cdot sin(\theta_z) & -sin(\theta_x) \cdot sin(\theta_y) \cdot sin(\theta_z) + cos(\theta_x) \cdot cos (\theta_z) & -sin(\theta_x) \cdot cos(\theta_y) & 0\\ -cos(\theta_x) \cdot sin(\theta_y) \cdot cos(\theta_z) + sin(\theta_x) \cdot sin(\theta_z) & cos(\theta_x) \cdot sin(\theta_y) \cdot sin(\theta_z) + sin(\theta_x) + cos(\theta_z) & cos(\theta_x) \cdot cos(\theta_y) & 0 \\ 0 & 0 & 0 & 1\end{pmatrix}\end{split}$$

The world matrix is:

$$ world = T \cdot R \cdot S $$

If the scale factors $\alpha$, $\beta$ and $\gamma$ are $1$ or we normalize first, then $T \cdot R = T \cdot R \cdot S$ because the $S$ is the identity matrix:

$$ T \cdot R = \begin{pmatrix}cos(\theta_y) \cdot cos(\theta_z) & -cos(\theta_y) \cdot sin(\theta_z) & sin(\theta_y) & t_x \\ sin(\theta_x) \cdot \sin(\theta_y) \cdot cos(\theta_z) + cos(\theta_x) \cdot sin(\theta_z) & -sin(\theta_x) \cdot sin(\theta_y) \cdot sin(\theta_z) + cos(\theta_x) \cdot cos (\theta_z) & -sin(\theta_x) \cdot cos(\theta_y) & t_y\\ -cos(\theta_x) \cdot sin(\theta_y) \cdot cos(\theta_z) + sin(\theta_x) \cdot sin(\theta_z) & cos(\theta_x) \cdot sin(\theta_y) \cdot sin(\theta_z) + sin(\theta_x) + cos(\theta_z) & cos(\theta_x) \cdot cos(\theta_y) & t_z \\ 0 & 0 & 0 & 1\end{pmatrix}$$

The first three entries in the first three columns give us the local axis in global coordinates for the $x$-, $y$- and $z$- axis respectively. This is because if we multiply the local axis vector with the matrix, the result is the same as the column in the world matrix:

$$ axis_{local x} = \begin{pmatrix} 1 \\ 0 \\ 0 \\ 0\end{pmatrix}$$

$$ axis_{local y} = \begin{pmatrix} 0 \\ 1 \\ 0 \\ 0\end{pmatrix}$$

$$ axis_{local z} = \begin{pmatrix} 0 \\ 0 \\ 1 \\ 0\end{pmatrix}$$

$$ world \cdot axis_{local\ x} = \begin{pmatrix}cos(\theta_y) \cdot cos(\theta_z) \\ sin(\theta_x) \cdot \sin(\theta_y) \cdot cos(\theta_z) + cos(\theta_x) \cdot sin(\theta_z) \\ -cos(\theta_x) \cdot sin(\theta_y) \cdot cos(\theta_z) + sin(\theta_x) \cdot sin(\theta_z)\\ 0\end{pmatrix}$$

$$ world \cdot axis_{local\ y} = \begin{pmatrix} -cos(\theta_y) \cdot sin(\theta_z) \\ -sin(\theta_x) \cdot sin(\theta_y) \cdot sin(\theta_z) + cos(\theta_x) \cdot cos (\theta_z) \\ cos(\theta_x) \cdot sin(\theta_y) \cdot sin(\theta_z) + sin(\theta_x) + cos(\theta_z) \\ 0 \end{pmatrix}$$

$$ world \cdot axis_{local\ z} = \begin{pmatrix} sin(\theta_y) \\ -sin(\theta_x) \cdot cos(\theta_y) \\ cos(\theta_x) \cdot cos(\theta_y) \\ 0 \end{pmatrix}$$

| improve this answer | |
$\endgroup$
  • $\begingroup$ Sorry for the initial confusion between local and global. Hope the answer is now clear. In local coordinates the y-axis is still unrotated, even if the object is rotated in global space. Therefore we need to multiply it with the rotation matrix to get the direction in global space. $\endgroup$ – Robert Gützkow Oct 22 '19 at 21:26
  • $\begingroup$ No worries...thanks for the great explanation. What I couldn't put together was how the world matrix connected to the local axis in a usable vector. Conceptually I get it, but get lost in the math a bit. With this I can standardize the approach in my scripts. $\endgroup$ – Sam Vimes Oct 22 '19 at 22:00
  • $\begingroup$ If .matrix_world (here,omw) is normalized to discard scale, the transformed Y axis is the first 3 elements of its second column, so how about [row[1] for row in omw.normalized()[:3]] ? .. or some better way of extracting it... $\endgroup$ – Robin Betts Oct 23 '19 at 6:49
  • 1
    $\begingroup$ @RobinBetts Yes that would work as well with 4x4 matrix, however the approach I described is more general and can be used for an arbitrary vector in local space. $\endgroup$ – Robert Gützkow Oct 23 '19 at 8:13
  • 1
    $\begingroup$ @RobinBetts I've added an explanation for your suggested approach, which requires less computation for the specific case of the local axis. $\endgroup$ – Robert Gützkow Oct 23 '19 at 10:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.