Class: Bestguigui::GLTFMath
- Inherits:
-
Object
- Object
- Bestguigui::GLTFMath
- Defined in:
- lib/bestguigui_lab/gltf_math.rb
Overview
Petite boite a outils matrices 4x4 / quaternions pour GLTFModel — matrices colonne-majeur (convention glTF). Jamais envoyees a la pile OpenGL (glLoadMatrixf) : uniquement du calcul CPU pour le skinning (voir GLTFPrimitive#skin!), donc aucune contrainte de convention avec le reste du kit (Render3D/ObjModel s'appuient sur glTranslatef/ glRotatef, jamais sur des matrices explicites). rubocop:disable Naming/MethodParameterName, Metrics/AbcSize, Style/ParallelAssignment -- noms mathematiques usuels (a, b, m, p, q, r, s, t, v...) et arithmetique vectorielle deroulee (pas de boucle a extraire) partout dans cette classe.
Class Method Summary collapse
- .eliminate_column(rows, inverse, col) ⇒ Object
- .identity ⇒ Object
-
.invert(m) ⇒ Object
Inverse generale d'une matrice 4x4, par elimination de Gauss-Jordan — necessaire pour repasser de l'espace monde d'un noeud de maillage a son espace local (cf. GLTFSkeleton#joint_matrices).
-
.matrix_from_trs(t, r, s) ⇒ Object
Compose T * R * S : t/s par defaut [0,0,0]/[1,1,1], r (quaternion [x, y, z, w]) par defaut la rotation identite.
-
.multiply(a, b) ⇒ Object
c = a * b (b applique en premier).
- .negate(v) ⇒ Object
- .normalize_quat(q) ⇒ Object
-
.quat_slerp(a, b, t) ⇒ Object
Interpolation spherique entre deux quaternions [x, y, z, w] ; bascule sur un lerp normalise si l'angle est trop petit pour diviser dessus.
- .quat_to_matrix(q) ⇒ Object
- .slerp_by_angle(a, b, dot, t) ⇒ Object
- .transform_point(m, p) ⇒ Object
-
.transform_vector(m, v) ⇒ Object
Comme transform_point, sans la translation — pour une normale.
- .vec3_lerp(a, b, t) ⇒ Object
- .vec4_lerp(a, b, t) ⇒ Object
Class Method Details
.eliminate_column(rows, inverse, col) ⇒ Object
135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/bestguigui_lab/gltf_math.rb', line 135 def self.eliminate_column(rows, inverse, col) 4.times do |row| next if row == col factor = rows[row][col] 4.times do |k| rows[row][k] -= factor * rows[col][k] inverse[row][k] -= factor * inverse[col][k] end end end |
.identity ⇒ Object
14 15 16 17 18 19 20 21 |
# File 'lib/bestguigui_lab/gltf_math.rb', line 14 def self.identity [ 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 |
.invert(m) ⇒ Object
Inverse generale d'une matrice 4x4, par elimination de Gauss-Jordan — necessaire pour repasser de l'espace monde d'un noeud de maillage a son espace local (cf. GLTFSkeleton#joint_matrices). Rare dans la boucle de rendu (quelques appels par frame, pas par sommet) : la simplicite prime ici sur la performance. rubocop:disable-next Metrics/MethodLength -- élimination de Gauss-Jordan, ne se découpe pas proprement
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/bestguigui_lab/gltf_math.rb', line 113 def self.invert(m) rows = m.each_slice(4).to_a.transpose.map(&:dup) inverse = Array.new(4) { |i| Array.new(4) { |j| i == j ? 1.0 : 0.0 } } 4.times do |col| pivot_row = (col...4).max_by { |r| rows[r][col].abs } rows[col], rows[pivot_row] = rows[pivot_row], rows[col] inverse[col], inverse[pivot_row] = inverse[pivot_row], inverse[col] pivot = rows[col][col] raise ArgumentError, 'matrice non inversible' if pivot.abs < 1e-9 4.times do |k| rows[col][k] /= pivot inverse[col][k] /= pivot end eliminate_column(rows, inverse, col) end inverse.transpose.flatten end |
.matrix_from_trs(t, r, s) ⇒ Object
Compose T * R * S : t/s par defaut [0,0,0]/[1,1,1], r (quaternion [x, y, z, w]) par defaut la rotation identite.
25 26 27 28 29 30 31 |
# File 'lib/bestguigui_lab/gltf_math.rb', line 25 def self.matrix_from_trs(t, r, s) m = quat_to_matrix(r || [0.0, 0.0, 0.0, 1.0]) scale = s || [1.0, 1.0, 1.0] (0..2).each { |col| (0..2).each { |row| m[(col * 4) + row] *= scale[col] } } m[12], m[13], m[14] = t || [0.0, 0.0, 0.0] m end |
.multiply(a, b) ⇒ Object
c = a * b (b applique en premier).
47 48 49 50 51 52 53 |
# File 'lib/bestguigui_lab/gltf_math.rb', line 47 def self.multiply(a, b) Array.new(16) do |i| col = i / 4 row = i % 4 (0..3).sum { |k| a[(k * 4) + row] * b[(col * 4) + k] } end end |
.negate(v) ⇒ Object
91 |
# File 'lib/bestguigui_lab/gltf_math.rb', line 91 def self.negate(v) = v.map(&:-@) |
.normalize_quat(q) ⇒ Object
93 94 95 96 |
# File 'lib/bestguigui_lab/gltf_math.rb', line 93 def self.normalize_quat(q) length = Math.sqrt(q.sum { |c| c * c }) q.map { |c| c / length } end |
.quat_slerp(a, b, t) ⇒ Object
Interpolation spherique entre deux quaternions [x, y, z, w] ; bascule sur un lerp normalise si l'angle est trop petit pour diviser dessus.
81 82 83 84 85 86 87 |
# File 'lib/bestguigui_lab/gltf_math.rb', line 81 def self.quat_slerp(a, b, t) dot = (0..3).sum { |i| a[i] * b[i] } b, dot = negate(b), -dot if dot.negative? return normalize_quat(vec4_lerp(a, b, t)) if dot > 0.9995 slerp_by_angle(a, b, dot, t) end |
.quat_to_matrix(q) ⇒ Object
33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/bestguigui_lab/gltf_math.rb', line 33 def self.quat_to_matrix(q) x, y, z, w = q xx, yy, zz = x * x, y * y, z * z xy, xz, yz = x * y, x * z, y * z wx, wy, wz = w * x, w * y, w * z [ 1 - (2 * (yy + zz)), 2 * (xy + wz), 2 * (xz - wy), 0.0, 2 * (xy - wz), 1 - (2 * (xx + zz)), 2 * (yz + wx), 0.0, 2 * (xz + wy), 2 * (yz - wx), 1 - (2 * (xx + yy)), 0.0, 0.0, 0.0, 0.0, 1.0 ] end |
.slerp_by_angle(a, b, dot, t) ⇒ Object
98 99 100 101 102 103 104 105 |
# File 'lib/bestguigui_lab/gltf_math.rb', line 98 def self.slerp_by_angle(a, b, dot, t) theta0 = Math.acos(dot.clamp(-1.0, 1.0)) sin_theta0 = Math.sin(theta0) theta = theta0 * t s0 = Math.cos(theta) - (dot * Math.sin(theta) / sin_theta0) s1 = Math.sin(theta) / sin_theta0 (0..3).map { |i| (a[i] * s0) + (b[i] * s1) } end |
.transform_point(m, p) ⇒ Object
55 56 57 58 59 60 61 62 |
# File 'lib/bestguigui_lab/gltf_math.rb', line 55 def self.transform_point(m, p) x, y, z = p [ (m[0] * x) + (m[4] * y) + (m[8] * z) + m[12], (m[1] * x) + (m[5] * y) + (m[9] * z) + m[13], (m[2] * x) + (m[6] * y) + (m[10] * z) + m[14] ] end |
.transform_vector(m, v) ⇒ Object
Comme transform_point, sans la translation — pour une normale. Simplification : ignore l'inverse-transposee (correcte pour une echelle uniforme, l'essentiel des rigs de jam ; peut legerement deformer une normale sous une echelle non uniforme sur un joint).
68 69 70 71 72 73 74 75 |
# File 'lib/bestguigui_lab/gltf_math.rb', line 68 def self.transform_vector(m, v) x, y, z = v [ (m[0] * x) + (m[4] * y) + (m[8] * z), (m[1] * x) + (m[5] * y) + (m[9] * z), (m[2] * x) + (m[6] * y) + (m[10] * z) ] end |
.vec3_lerp(a, b, t) ⇒ Object
77 |
# File 'lib/bestguigui_lab/gltf_math.rb', line 77 def self.vec3_lerp(a, b, t) = (0..2).map { |i| a[i] + ((b[i] - a[i]) * t) } |
.vec4_lerp(a, b, t) ⇒ Object
89 |
# File 'lib/bestguigui_lab/gltf_math.rb', line 89 def self.vec4_lerp(a, b, t) = (0..3).map { |i| a[i] + ((b[i] - a[i]) * t) } |