module Mittsu class Euler RotationOrders = [ 'XYZ', 'YZX', 'ZXY', 'XZY', 'YXZ', 'ZYX' ] DefaultOrder = 'XYZ' attr_reader :x, :y, :z, :order def initialize(x = 0.0, y = 0.0, z = 0.0, order = DefaultOrder) @x, @y, @z, @order = x.to_f, y.to_f, z.to_f, order @on_change_callback = nil end def set(x, y, z, order = nil) @x = x.to_f @y = y.to_f @z = z.to_f @order = order || @order self.on_change_callback self end def x=(x) @x = x.to_f self.on_change_callback end def y=(y) @y = y.to_f self.on_change_callback end def z=(z) @z = z.to_f self.on_change_callback end def order=(order) @order = order self.on_change_callback end def copy(euler) @x = euler.x @y = euler.y @z = euler.z @order = euler.order self.on_change_callback self end def set_from_rotation_matrix(m, order, update = true) # assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) te = m.elements m11 = te[0]; m12 = te[4]; m13 = te[8] m21 = te[1]; m22 = te[5]; m23 = te[9] m31 = te[2]; m32 = te[6]; m33 = te[10] order = order || @order if order == 'XYZ' @y = ::Math.asin(Math.clamp(m13, -1.0, 1.0)) if m13.abs < 0.99999 @x = ::Math.atan2(- m23, m33) @z = ::Math.atan2(- m12, m11) else @x = ::Math.atan2(m32, m22) @z = 0.0 end elsif order == 'YXZ' @x = ::Math.asin(- Math.clamp(m23, -1.0, 1.0)) if m23.abs < 0.99999 @y = ::Math.atan2(m13, m33) @z = ::Math.atan2(m21, m22) else @y = ::Math.atan2(- m31, m11) @z = 0.0 end elsif order == 'ZXY' @x = ::Math.asin(Math.clamp(m32, -1.0, 1.0)) if m32.abs < 0.99999 @y = ::Math.atan2(- m31, m33) @z = ::Math.atan2(- m12, m22) else @y = 0.0 @z = ::Math.atan2(m21, m11) end elsif order == 'ZYX' @y = ::Math.asin(- Math.clamp(m31, -1.0, 1.0)) if m31.abs < 0.99999 @x = ::Math.atan2(m32, m33) @z = ::Math.atan2(m21, m11) else @x = 0.0 @z = ::Math.atan2(- m12, m22) end elsif order == 'YZX' @z = ::Math.asin(Math.clamp(m21, -1.0, 1.0)) if m21.abs < 0.99999 @x = ::Math.atan2(- m23, m22) @y = ::Math.atan2(- m31, m11) else @x = 0.0 @y = ::Math.atan2(m13, m33) end elsif order == 'XZY' @z = ::Math.asin(- Math.clamp(m12, -1.0, 1.0)) if m12.abs < 0.99999 @x = ::Math.atan2(m32, m22) @y = ::Math.atan2(m13, m11) else @x = ::Math.atan2(- m23, m33) @y = 0.0 end else puts("WARNING: Mittsu::Euler#set_from_rotation_matrix given unsupported order: #{order}") end @order = order self.on_change_callback if update self end def set_from_quaternion(q, order, update = true) matrix = Mittsu::Matrix4.new matrix.make_rotation_from_quaternion(q) self.set_from_rotation_matrix(matrix, order, update) self end def set_from_vector3(v, order) self.set(v.x, v.y, v.z, order || @order) end def reorder(new_order) # WARNING: this discards revolution information -bhouston q = Mittsu::Quaternion.new.set_from_euler(self) self.set_from_quaternion(q, new_order) end def ==(euler) (euler.x == @x) && (euler.y == @y) && (euler.z == @z) && (euler.order == @order) end def from_array(array) @x = array[0] @y = array[1] @z = array[2] @order = array[3] unless array[3].nil? self.on_change_callback self end def to_a(array = [], offset = 0) array[offset] = @x array[offset + 1] = @y array[offset + 2] = @z array[offset + 3] = @order array end def to_vector3(optional_result = nil) if optional_result return optional_result.set(@x, @y, @z) else return Mittsu::Vector3.new(@x, @y, @z) end end def on_change(&callback) @on_change_callback = callback self end def on_change_callback return unless @on_change_callback @on_change_callback.call end def clone Mittsu::Euler.new(@x, @y, @z, @order) end end end