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