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 }