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 }