require 'mittsu/math' module Mittsu class Matrix4 attr_accessor :elements DIMENSIONS = 4 def initialize() @elements = [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] end def set(n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44) te = self.elements te[0] = n11.to_f; te[4] = n12.to_f; te[8] = n13.to_f; te[12] = n14.to_f te[1] = n21.to_f; te[5] = n22.to_f; te[9] = n23.to_f; te[13] = n24.to_f te[2] = n31.to_f; te[6] = n32.to_f; te[10] = n33.to_f; te[14] = n34.to_f te[3] = n41.to_f; te[7] = n42.to_f; te[11] = n43.to_f; te[15] = n44.to_f self end def identity self.set( 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ) self end def copy(m) self.from_array(m.elements) self end def copy_position(m) te = self.elements me = m.elements te[12] = me[12] te[13] = me[13] te[14] = me[14] self end def extract_basis(x_axis, y_axis, z_axis) te = self.elements x_axis.set(te[0], te[1], te[2]) y_axis.set(te[4], te[5], te[6]) z_axis.set(te[8], te[9], te[10]) self end def make_basis(x_axis, y_axis, z_axis) self.set( x_axis.x, y_axis.x, z_axis.x, 0.0, x_axis.y, y_axis.y, z_axis.y, 0.0, x_axis.z, y_axis.z, z_axis.z, 0.0, 0.0, 0.0, 0.0, 1.0 ) self end def extract_rotation(m) v1 = Mittsu::Vector3.new te = self.elements me = m.elements scale_x = 1.0 / v1.set(me[0], me[1], me[2]).length scale_y = 1.0 / v1.set(me[4], me[5], me[6]).length scale_z = 1.0 / v1.set(me[8], me[9], me[10]).length te[0] = me[0] * scale_x te[1] = me[1] * scale_x te[2] = me[2] * scale_x te[4] = me[4] * scale_y te[5] = me[5] * scale_y te[6] = me[6] * scale_y te[8] = me[8] * scale_z te[9] = me[9] * scale_z te[10] = me[10] * scale_z self end def make_rotation_from_euler(euler) te = self.elements x, y, z = euler.x, euler.y, euler.z a, b = Math.cos(x), Math.sin(x) c, d = Math.cos(y), Math.sin(y) e, f = Math.cos(z), Math.sin(z) if euler.order == 'XYZ' ae = a * e; af = a * f; be = b * e; bf = b * f te[0] = c * e te[4] = - c * f te[8] = d te[1] = af + be * d te[5] = ae - bf * d te[9] = - b * c te[2] = bf - ae * d te[6] = be + af * d te[10] = a * c elsif euler.order == 'YXZ' ce = c * e; cf = c * f; de = d * e; df = d * f te[0] = ce + df * b te[4] = de * b - cf te[8] = a * d te[1] = a * f te[5] = a * e te[9] = - b te[2] = cf * b - de te[6] = df + ce * b te[10] = a * c elsif euler.order == 'ZXY' ce = c * e; cf = c * f; de = d * e; df = d * f te[0] = ce - df * b te[4] = - a * f te[8] = de + cf * b te[1] = cf + de * b te[5] = a * e te[9] = df - ce * b te[2] = - a * d te[6] = b te[10] = a * c elsif euler.order == 'ZYX' ae = a * e; af = a * f; be = b * e; bf = b * f te[0] = c * e te[4] = be * d - af te[8] = ae * d + bf te[1] = c * f te[5] = bf * d + ae te[9] = af * d - be te[2] = - d te[6] = b * c te[10] = a * c elsif euler.order == 'YZX' ac = a * c; ad = a * d; bc = b * c; bd = b * d te[0] = c * e te[4] = bd - ac * f te[8] = bc * f + ad te[1] = f te[5] = a * e te[9] = - b * e te[2] = - d * e te[6] = ad * f + bc te[10] = ac - bd * f elsif euler.order == 'XZY' ac = a * c; ad = a * d; bc = b * c; bd = b * d te[0] = c * e te[4] = - f te[8] = d * e te[1] = ac * f + bd te[5] = a * e te[9] = ad * f - bc te[2] = bc * f - ad te[6] = b * e te[10] = bd * f + ac end # last column te[3] = 0.0 te[7] = 0.0 te[11] = 0.0 # bottom row te[12] = 0.0 te[13] = 0.0 te[14] = 0.0 te[15] = 1.0 self end def make_rotation_from_quaternion(q) te = self.elements x, y, z, w = q.x, q.y, q.z, q.w x2, y2, z2 = x + x, y + y, z + z xx, xy, xz = x * x2, x * y2, x * z2 yy, yz, zz = y * y2, y * z2, z * z2 wx, wy, wz = w * x2, w * y2, w * z2 te[0] = 1.0 - (yy + zz) te[4] = xy - wz te[8] = xz + wy te[1] = xy + wz te[5] = 1.0 - (xx + zz) te[9] = yz - wx te[2] = xz - wy te[6] = yz + wx te[10] = 1.0 - (xx + yy) # last column te[3] = 0.0 te[7] = 0.0 te[11] = 0.0 # bottom row te[12] = 0.0 te[13] = 0.0 te[14] = 0.0 te[15] = 1.0 self end def look_at(eye, target, up) x = Mittsu::Vector3.new y = Mittsu::Vector3.new z = Mittsu::Vector3.new te = self.elements z.sub_vectors(eye, target).normalize if z.length.zero? z.z = 1.0 end x.cross_vectors(up, z).normalize if x.length.zero? z.x += 0.0001 x.cross_vectors(up, z).normalize end y.cross_vectors(z, x) te[0] = x.x; te[4] = y.x; te[8] = z.x te[1] = x.y; te[5] = y.y; te[9] = z.y te[2] = x.z; te[6] = y.z; te[10] = z.z self end def multiply(m) self.multiply_matrices(self, m) end def multiply_matrices(a, b) ae = a.elements be = b.elements te = self.elements a11 = ae[0]; a12 = ae[4]; a13 = ae[8]; a14 = ae[12] a21 = ae[1]; a22 = ae[5]; a23 = ae[9]; a24 = ae[13] a31 = ae[2]; a32 = ae[6]; a33 = ae[10]; a34 = ae[14] a41 = ae[3]; a42 = ae[7]; a43 = ae[11]; a44 = ae[15] b11 = be[0]; b12 = be[4]; b13 = be[8]; b14 = be[12] b21 = be[1]; b22 = be[5]; b23 = be[9]; b24 = be[13] b31 = be[2]; b32 = be[6]; b33 = be[10]; b34 = be[14] b41 = be[3]; b42 = be[7]; b43 = be[11]; b44 = be[15] te[0] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41 te[4] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42 te[8] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43 te[12] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44 te[1] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41 te[5] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42 te[9] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43 te[13] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44 te[2] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41 te[6] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42 te[10] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43 te[14] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44 te[3] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41 te[7] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42 te[11] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43 te[15] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44 self end def multiply_to_array(a, b, r) te = self.elements self.multiply_matrices(a, b) r[0] = te[0]; r[1] = te[1]; r[2] = te[2]; r[3] = te[3] r[4] = te[4]; r[5] = te[5]; r[6] = te[6]; r[7] = te[7] r[8] = te[8]; r[9] = te[9]; r[10] = te[10]; r[11] = te[11] r[12] = te[12]; r[13] = te[13]; r[14] = te[14]; r[15] = te[15] self end def multiply_scalar(s) te = self.elements s = s.to_f te[0] *= s; te[4] *= s; te[8] *= s; te[12] *= s te[1] *= s; te[5] *= s; te[9] *= s; te[13] *= s te[2] *= s; te[6] *= s; te[10] *= s; te[14] *= s te[3] *= s; te[7] *= s; te[11] *= s; te[15] *= s self end def apply_to_vector3_array(array, offset = 0, length = array.length) v1 = Mittsu::Vector3.new i = 0 j = offset while i < length v1.x = array[j].to_f v1.y = array[j + 1].to_f v1.z = array[j + 2].to_f v1.apply_matrix4(self) array[j] = v1.x array[j + 1] = v1.y array[j + 2] = v1.z i += 3 j += 3 end array end def determinant te = self.elements n11 = te[0]; n12 = te[4]; n13 = te[8]; n14 = te[12] n21 = te[1]; n22 = te[5]; n23 = te[9]; n24 = te[13] n31 = te[2]; n32 = te[6]; n33 = te[10]; n34 = te[14] n41 = te[3]; n42 = te[7]; n43 = te[11]; n44 = te[15] #TODO: make this more efficient #(based on http:#www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm) n41 * (n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34) + n42 * (n11 * n23 * n34 - n11 * n24 * n33 + n14 * n21 * n33 - n13 * n21 * n34 + n13 * n24 * n31 - n14 * n23 * n31) + n43 * (n11 * n24 * n32 - n11 * n22 * n34 - n14 * n21 * n32 + n12 * n21 * n34 + n14 * n22 * n31 - n12 * n24 * n31) + n44 * (-n13 * n22 * n31 - n11 * n23 * n32 + n11 * n22 * n33 + n13 * n21 * n32 - n12 * n21 * n33 + n12 * n23 * n31) end def transpose te = self.elements tmp = te[1]; te[1] = te[4]; te[4] = tmp tmp = te[2]; te[2] = te[8]; te[8] = tmp tmp = te[6]; te[6] = te[9]; te[9] = tmp tmp = te[3]; te[3] = te[12]; te[12] = tmp tmp = te[7]; te[7] = te[13]; te[13] = tmp tmp = te[11]; te[11] = te[14]; te[14] = tmp self end def flatten_to_array_offset(array, offset) te = self.elements array[offset ] = te[0] array[offset + 1] = te[1] array[offset + 2] = te[2] array[offset + 3] = te[3] array[offset + 4] = te[4] array[offset + 5] = te[5] array[offset + 6] = te[6] array[offset + 7] = te[7] array[offset + 8] = te[8] array[offset + 9] = te[9] array[offset + 10] = te[10] array[offset + 11] = te[11] array[offset + 12] = te[12] array[offset + 13] = te[13] array[offset + 14] = te[14] array[offset + 15] = te[15] array end def set_position(v) te = self.elements te[12] = v.x te[13] = v.y te[14] = v.z self end def inverse(m, throw_on_invertable = false) # based on http:#www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm te = @elements me = m.elements n11 = me[0]; n12 = me[4]; n13 = me[8]; n14 = me[12] n21 = me[1]; n22 = me[5]; n23 = me[9]; n24 = me[13] n31 = me[2]; n32 = me[6]; n33 = me[10]; n34 = me[14] n41 = me[3]; n42 = me[7]; n43 = me[11]; n44 = me[15] te[0] = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44 te[4] = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44 te[8] = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44 te[12] = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34 te[1] = n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44 te[5] = n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44 te[9] = n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44 te[13] = n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34 te[2] = n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44 te[6] = n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44 te[10] = n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44 te[14] = n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34 te[3] = n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43 te[7] = n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43 te[11] = n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43 te[15] = n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33 det = n11 * te[0] + n21 * te[4] + n31 * te[8] + n41 * te[12] if det.zero? msg = "Mittsu::Matrix4#inverse: can't invert matrix, determinant is 0" if throw_on_invertable raise Error.new(msg) else # THREE.warn(msg) puts "WARNING: #{msg}" end self.identity return self end self.multiply_scalar(1.0 / det) self end def scale(v) te = self.elements x = v.x; y = v.y; z = v.z te[0] *= x; te[4] *= y; te[8] *= z te[1] *= x; te[5] *= y; te[9] *= z te[2] *= x; te[6] *= y; te[10] *= z te[3] *= x; te[7] *= y; te[11] *= z self end def max_scale_on_axis te = self.elements scale_x_sq = te[0] * te[0] + te[1] * te[1] + te[2] * te[2] scale_y_sq = te[4] * te[4] + te[5] * te[5] + te[6] * te[6] scale_z_sq = te[8] * te[8] + te[9] * te[9] + te[10] * te[10] Math.sqrt([scale_x_sq, scale_y_sq, scale_z_sq].max) end def make_translation(x, y, z) self.set( 1.0, 0.0, 0.0, x.to_f, 0.0, 1.0, 0.0, y.to_f, 0.0, 0.0, 1.0, z.to_f, 0.0, 0.0, 0.0, 1.0 ) self end def make_rotation_x(theta) c, s = Math.cos(theta), Math.sin(theta) self.set( 1.0, 0.0, 0.0, 0.0, 0.0, c, -s, 0.0, 0.0, s, c, 0.0, 0.0, 0.0, 0.0, 1.0 ) self end def make_rotation_y(theta) c, s = Math.cos(theta), Math.sin(theta) self.set( c, 0.0, s, 0.0, 0.0, 1.0, 0.0, 0.0, -s, 0.0, c, 0.0, 0.0, 0.0, 0.0, 1.0 ) self end def make_rotation_z(theta) c, s = Math.cos(theta), Math.sin(theta) self.set( c, -s, 0.0, 0.0, s, c, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ) self end def make_rotation_axis(axis, angle) # Based on http:#www.gamedev.net/reference/articles/article1199.asp c = Math.cos(angle) s = Math.sin(angle) t = 1.0 - c x, y, z = axis.x, axis.y, axis.z tx, ty = t * x, t * y self.set( tx * x + c, tx * y - s * z, tx * z + s * y, 0.0, tx * y + s * z, ty * y + c, ty * z - s * x, 0.0, tx * z - s * y, ty * z + s * x, t * z * z + c, 0.0, 0.0, 0.0, 0.0, 1.0 ) self end def make_scale(x, y, z) self.set( x.to_f, 0.0, 0.0, 0.0, 0.0, y.to_f, 0.0, 0.0, 0.0, 0.0, z.to_f, 0.0, 0.0, 0.0, 0.0, 1.0 ) self end def compose(position, quaternion, scale) self.make_rotation_from_quaternion(quaternion) self.scale(scale) self.set_position(position) self end def decompose(position, quaternion, scale) vector = Mittsu::Vector3.new matrix = Mittsu::Matrix4.new te = self.elements sx = vector.set(te[0], te[1], te[2]).length sy = vector.set(te[4], te[5], te[6]).length sz = vector.set(te[8], te[9], te[10]).length # if determine is negative, we need to invert one scale det = self.determinant if det < 0.0 sx = -sx end position.x = te[12] position.y = te[13] position.z = te[14] # scale the rotation part matrix.elements[0...15] = self.elements # at this point matrix is incomplete so we can't use .copy inv_sx = 1.0 / sx inv_sy = 1.0 / sy inv_sz = 1.0 / sz matrix.elements[0] *= inv_sx matrix.elements[1] *= inv_sx matrix.elements[2] *= inv_sx matrix.elements[4] *= inv_sy matrix.elements[5] *= inv_sy matrix.elements[6] *= inv_sy matrix.elements[8] *= inv_sz matrix.elements[9] *= inv_sz matrix.elements[10] *= inv_sz quaternion.set_from_rotation_matrix(matrix) scale.x = sx scale.y = sy scale.z = sz self end def make_frustum(left, right, bottom, top, near, far) left, right, bottom, top, near, far = left.to_f, right.to_f, bottom.to_f, top.to_f, near.to_f, far.to_f te = self.elements x = 2.0 * near / (right - left) y = 2.0 * near / (top - bottom) a = (right + left) / (right - left) b = (top + bottom) / (top - bottom) c = -(far + near) / (far - near) d = -2.0 * far * near / (far - near) te[0] = x; te[4] = 0.0; te[8] = a; te[12] = 0.0 te[1] = 0.0; te[5] = y; te[9] = b; te[13] = 0.0 te[2] = 0.0; te[6] = 0.0; te[10] = c; te[14] = d te[3] = 0.0; te[7] = 0.0; te[11] = -1.0; te[15] = 0.0 self end def make_perspective(fov, aspect, near, far) fov, aspect, near, far = fov.to_f, aspect.to_f, near.to_f, far.to_f ymax = near * Math.tan(Math.deg_to_rad(fov * 0.5)) ymin = -ymax xmin = ymin * aspect xmax = ymax * aspect self.make_frustum(xmin, xmax, ymin, ymax, near, far) end def make_orthographic(left, right, top, bottom, near, far) left, right, top, bottom, near, far = left.to_f, right.to_f, top.to_f, bottom.to_f, near.to_f, far.to_f te = self.elements w = right - left h = top - bottom p = far - near x = (right + left) / w y = (top + bottom) / h z = (far + near) / p te[0] = 2.0 / w; te[4] = 0.0; te[8] = 0.0; te[12] = -x te[1] = 0.0; te[5] = 2.0 / h; te[9] = 0.0; te[13] = -y te[2] = 0.0; te[6] = 0.0; te[10] = -2.0 / p; te[14] = -z te[3] = 0.0; te[7] = 0.0; te[11] = 0.0; te[15] = 1.0 self end def from_array(array) self.elements[0..array.length] = array self end def ==(other) other.elements == @elements end def to_a te = self.elements [ te[0], te[1], te[2], te[3], te[4], te[5], te[6], te[7], te[8], te[9], te[10], te[11], te[12], te[13], te[14], te[15] ] end def clone Mittsu::Matrix4.new.from_array(self.elements) end end end