1 /** 2d/3d combined matrix transformation abstraction layer */ 2 3 module re.math.transform; 4 5 import re.math; 6 7 /// represents an object transform in both 2d and 3d space 8 struct Transform { 9 /* 10 11 The "dirty" system: 12 13 used internally to keep track of what parts of the transform have been modified 14 and are thus "out-of-sync" with its transformation matrices. 15 16 for example, when you modify rotation via the Z-angle, rotation will be set dirty, 17 and the transform will be synchronized to reflect that new transform. in addition, 18 the orientation quaternion will also be updated so it is in sync. 19 20 this helps save computation: since position and scale were not modified, there's 21 no reason to recompute their matrices; we can reuse our cached matrices for them. 22 whenever a part of the transform is modified, its matrix is marked dirty, 23 so it alone can be recomputed. 24 25 */ 26 27 /// whether anything is dirty 28 private bool _dirty; 29 /// whether scale is dirty 30 private bool _dirty_scale; 31 /// whether position is dirty 32 private bool _dirty_position; 33 /// whether rotation is dirty 34 private bool _dirty_rotation; 35 /// whether the dirty rotation is the Z-rotation 36 private bool _dirty_rotation_z; 37 /// whether the dirty rotation is the quaternion 38 private bool _dirty_rotation_quat; 39 40 private Vector3 _position = Vector3(0, 0, 0); 41 private Vector3 _scale = Vector3(1, 1, 1); 42 private float _rotation_z = 0; 43 private Quaternion _rotation_quat = raymath.QuaternionIdentity(); 44 private AxisAngle _rotation_axis_angle = AxisAngle(Vector3(0, 0, 1), 0); 45 46 /// transform matrix for local scale 47 private Matrix _scl_mat = raymath.MatrixIdentity(); 48 /// transform matrix for local position 49 private Matrix _pos_mat = raymath.MatrixIdentity(); 50 /// transform matrix for local rotation 51 private Matrix _rot_mat = raymath.MatrixIdentity(); 52 53 /// transform matrix from local to world 54 private Matrix _localToWorldTransform = raymath.MatrixIdentity(); 55 /// transform matrix from world to local 56 private Matrix _worldToLocalTransform = raymath.MatrixIdentity(); 57 58 // 2d wrappers 59 60 /// gets 2d position 61 @property Vector2 position2() { 62 auto pos = position; 63 return Vector2(pos.x, pos.y); 64 } 65 66 /// sets 2d position 67 @property Vector2 position2(Vector2 value) { 68 position = Vector3(value.x, value.y, 0); 69 return value; 70 } 71 72 /// gets 2d scale 73 @property Vector2 scale2() { 74 auto scl = scale; 75 return Vector2(scl.x, scl.y); 76 } 77 78 /// sets 2d scale 79 @property Vector2 scale2(Vector2 value) { 80 scale = Vector3(value.x, value.y, 1); 81 return value; 82 } 83 84 // main sub-transforms 85 86 /// gets 3d position 87 @property ref Vector3 position() return { 88 update_transform(); 89 return _position; 90 } 91 92 /// sets 3d position 93 @property Vector3 position(Vector3 value) { 94 _dirty = _dirty_position = true; 95 return _position = value; 96 } 97 98 /// gets 3d scale 99 @property ref Vector3 scale() return { 100 update_transform(); 101 return _scale; 102 } 103 104 /// sets 3d scale 105 @property Vector3 scale(Vector3 value) { 106 _dirty = _dirty_scale = true; 107 return _scale = value; 108 } 109 110 /// gets Z-axis rotation 111 @property float rotation_z() { 112 update_transform(); 113 return _rotation_z; 114 } 115 116 /// sets Z-axis rotation 117 @property float rotation_z(float radians) { 118 _dirty = _dirty_rotation = _dirty_rotation_z = true; 119 _rotation_z = radians % C_2_PI; 120 return radians; 121 } 122 123 /// gets orientation quaternion 124 @property Quaternion orientation() { 125 update_transform(); 126 return _rotation_quat; 127 } 128 129 /// sets orientation quaternion 130 @property Quaternion orientation(Quaternion value) { 131 _dirty = _dirty_rotation = _dirty_rotation_quat = true; 132 _rotation_quat = value; 133 return value; 134 } 135 136 /// sets orientation quaternion from euler angles 137 @property Vector3 orientation(Vector3 value) { 138 orientation = raymath.QuaternionFromEuler(value.x, value.y, value.z); 139 return value; 140 } 141 142 /// gets orientation as an axis angle 143 @property AxisAngle axis_angle() { 144 update_transform(); 145 return _rotation_axis_angle; 146 } 147 148 /// gets local-to-world transform 149 @property Matrix local_to_world_transform() { 150 update_transform(); 151 return _localToWorldTransform; 152 } 153 154 /// gets world-to-local transform 155 @property Matrix world_to_local_transform() { 156 update_transform(); 157 return _worldToLocalTransform; 158 } 159 160 private void update_transform() { 161 if (!_dirty) 162 return; 163 164 // recompute matrices 165 if (_dirty_position) { 166 _pos_mat = raymath.MatrixTranslate(_position.x, _position.y, _position.z); 167 _dirty_position = false; 168 } 169 if (_dirty_rotation) { 170 if (_dirty_rotation_z) { 171 _rot_mat = raymath.MatrixRotateZ(_rotation_z); 172 _dirty_rotation_z = false; 173 // sync Z-rotation to quaternion 174 _rotation_quat = raymath.QuaternionFromMatrix(_rot_mat); 175 } 176 if (_dirty_rotation_quat) { 177 // recompute rotation matrix from quaternion 178 _rot_mat = raymath.QuaternionToMatrix(_rotation_quat); 179 _dirty_rotation_quat = false; 180 // sync quaternion to Z-rotation 181 immutable auto euler_angles = raymath.QuaternionToEuler(_rotation_quat); 182 _rotation_z = euler_angles.z * C_DEG2RAD; 183 } 184 // sync rotation quaternion to axis angle 185 raymath.QuaternionToAxisAngle(_rotation_quat, 186 &_rotation_axis_angle.axis, &_rotation_axis_angle.angle); 187 188 // rotation is up to date 189 _dirty_rotation = false; 190 } 191 if (_dirty_scale) { 192 _scl_mat = raymath.MatrixScale(_scale.x, _scale.y, _scale.z); 193 _dirty_scale = false; 194 } 195 196 auto tmp1 = raymath.MatrixMultiply(_scl_mat, _rot_mat); 197 auto tmp2 = raymath.MatrixMultiply(tmp1, _pos_mat); 198 199 _localToWorldTransform = tmp2; 200 _worldToLocalTransform = raymath.MatrixInvert(_localToWorldTransform); 201 202 _dirty = false; 203 } 204 205 public string toString() { 206 import std.string : format; 207 208 import re.util.format : format_vec; 209 210 return format("[pos: %s, ori: %s, scl: %s]", position.format_vec(2), 211 orientation.format_vec(2), scale.format_vec(2)); 212 } 213 }