Mathematically decomposing a transformation matrix does not give a single solution (e.g. +170° and -190°). If you have bad luck, the component is skewed/sheared and you get nonesense. Under the premise that there is no shear, it is possible.
# Separate translation, scaling and rotation components of a 4×4
# augmented rotation matrix without shear.
#
# @param m [Array(16)] matrix
# @returns three arrays of three floats
def separate_translation_scaling_rotation(m)
m = m.clone
# Extract translation
translation = m.values_at(12, 13, 14)
# Extract scaling, considering uniform scale factor (last matrix element)
scaling = Array.new(3)
scaling[0] = m[15] * Math.sqrt(m[0]**2 + m[1]**2 + m[2]**2)
scaling[1] = m[15] * Math.sqrt(m[4]**2 + m[5]**2 + m[6]**2)
scaling[2] = m[15] * Math.sqrt(m[8]**2 + m[9]**2 + m[10]**2)
# Remove scaling to prepare for extraction of rotation
[0,1,2].each{ |i| m[i] /= scaling[0] } unless scaling[0] == 0.0
[4,5,6].each{ |i| m[i] /= scaling[1] } unless scaling[1] == 0.0
[8,9,10].each{ |i| m[i] /= scaling[2] } unless scaling[2] == 0.0
m[15] = 1.0
# Verify orientation
tmp_z_axis = Geom::Vector3d.new(m[0], m[1], m[2]).cross(Geom::Vector3d.new(m[4], m[5], m[6]))
if tmp_z_axis.dot( Geom::Vector3d.new(m[8], m[9], m[10]) ) < 0
scaling[0] *= -1
m[0] = -m[0]
m[1] = -m[1]
m[2] = -m[2]
end
# Extract rotation
# Source: Extracting Euler Angles from a Rotation Matrix, Mike Day, Insomniac Games
# http://www.insomniacgames.com/mike-day-extracting-euler-angles-from-a-rotation-matrix/
theta1 = Math.atan2(m[6], m[10])
c2 = Math.sqrt(m[0]**2 + m[1]**2)
theta2 = Math.atan2(-m[2], c2)
s1 = Math.sin(theta1)
c1 = Math.cos(theta1)
theta3 = Math.atan2(s1*m[8] - c1*m[4], c1*m[5] - s1*m[9])
rotation = [-theta1, -theta2, -theta3]
return translation, scaling, rotation
end
The dynamic components extension extends core ruby classes which is not recommended. Since SketchUp has no dependency resolving package manager, your users will report to you errors when they have not installed and activated dynamic components. Better have the code in your own extension.