1/**************************************************************************/
2/* transform_2d.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "transform_2d.h"
32
33#include "core/string/ustring.h"
34
35void Transform2D::invert() {
36 // FIXME: this function assumes the basis is a rotation matrix, with no scaling.
37 // Transform2D::affine_inverse can handle matrices with scaling, so GDScript should eventually use that.
38 SWAP(columns[0][1], columns[1][0]);
39 columns[2] = basis_xform(-columns[2]);
40}
41
42Transform2D Transform2D::inverse() const {
43 Transform2D inv = *this;
44 inv.invert();
45 return inv;
46}
47
48void Transform2D::affine_invert() {
49 real_t det = determinant();
50#ifdef MATH_CHECKS
51 ERR_FAIL_COND(det == 0);
52#endif
53 real_t idet = 1.0f / det;
54
55 SWAP(columns[0][0], columns[1][1]);
56 columns[0] *= Vector2(idet, -idet);
57 columns[1] *= Vector2(-idet, idet);
58
59 columns[2] = basis_xform(-columns[2]);
60}
61
62Transform2D Transform2D::affine_inverse() const {
63 Transform2D inv = *this;
64 inv.affine_invert();
65 return inv;
66}
67
68void Transform2D::rotate(const real_t p_angle) {
69 *this = Transform2D(p_angle, Vector2()) * (*this);
70}
71
72real_t Transform2D::get_skew() const {
73 real_t det = determinant();
74 return Math::acos(columns[0].normalized().dot(SIGN(det) * columns[1].normalized())) - (real_t)Math_PI * 0.5f;
75}
76
77void Transform2D::set_skew(const real_t p_angle) {
78 real_t det = determinant();
79 columns[1] = SIGN(det) * columns[0].rotated(((real_t)Math_PI * 0.5f + p_angle)).normalized() * columns[1].length();
80}
81
82real_t Transform2D::get_rotation() const {
83 return Math::atan2(columns[0].y, columns[0].x);
84}
85
86void Transform2D::set_rotation(const real_t p_rot) {
87 Size2 scale = get_scale();
88 real_t cr = Math::cos(p_rot);
89 real_t sr = Math::sin(p_rot);
90 columns[0][0] = cr;
91 columns[0][1] = sr;
92 columns[1][0] = -sr;
93 columns[1][1] = cr;
94 set_scale(scale);
95}
96
97Transform2D::Transform2D(const real_t p_rot, const Vector2 &p_pos) {
98 real_t cr = Math::cos(p_rot);
99 real_t sr = Math::sin(p_rot);
100 columns[0][0] = cr;
101 columns[0][1] = sr;
102 columns[1][0] = -sr;
103 columns[1][1] = cr;
104 columns[2] = p_pos;
105}
106
107Transform2D::Transform2D(const real_t p_rot, const Size2 &p_scale, const real_t p_skew, const Vector2 &p_pos) {
108 columns[0][0] = Math::cos(p_rot) * p_scale.x;
109 columns[1][1] = Math::cos(p_rot + p_skew) * p_scale.y;
110 columns[1][0] = -Math::sin(p_rot + p_skew) * p_scale.y;
111 columns[0][1] = Math::sin(p_rot) * p_scale.x;
112 columns[2] = p_pos;
113}
114
115Size2 Transform2D::get_scale() const {
116 real_t det_sign = SIGN(determinant());
117 return Size2(columns[0].length(), det_sign * columns[1].length());
118}
119
120void Transform2D::set_scale(const Size2 &p_scale) {
121 columns[0].normalize();
122 columns[1].normalize();
123 columns[0] *= p_scale.x;
124 columns[1] *= p_scale.y;
125}
126
127void Transform2D::scale(const Size2 &p_scale) {
128 scale_basis(p_scale);
129 columns[2] *= p_scale;
130}
131
132void Transform2D::scale_basis(const Size2 &p_scale) {
133 columns[0][0] *= p_scale.x;
134 columns[0][1] *= p_scale.y;
135 columns[1][0] *= p_scale.x;
136 columns[1][1] *= p_scale.y;
137}
138
139void Transform2D::translate_local(const real_t p_tx, const real_t p_ty) {
140 translate_local(Vector2(p_tx, p_ty));
141}
142
143void Transform2D::translate_local(const Vector2 &p_translation) {
144 columns[2] += basis_xform(p_translation);
145}
146
147void Transform2D::orthonormalize() {
148 // Gram-Schmidt Process
149
150 Vector2 x = columns[0];
151 Vector2 y = columns[1];
152
153 x.normalize();
154 y = y - x * x.dot(y);
155 y.normalize();
156
157 columns[0] = x;
158 columns[1] = y;
159}
160
161Transform2D Transform2D::orthonormalized() const {
162 Transform2D ortho = *this;
163 ortho.orthonormalize();
164 return ortho;
165}
166
167bool Transform2D::is_equal_approx(const Transform2D &p_transform) const {
168 return columns[0].is_equal_approx(p_transform.columns[0]) && columns[1].is_equal_approx(p_transform.columns[1]) && columns[2].is_equal_approx(p_transform.columns[2]);
169}
170
171bool Transform2D::is_finite() const {
172 return columns[0].is_finite() && columns[1].is_finite() && columns[2].is_finite();
173}
174
175Transform2D Transform2D::looking_at(const Vector2 &p_target) const {
176 Transform2D return_trans = Transform2D(get_rotation(), get_origin());
177 Vector2 target_position = affine_inverse().xform(p_target);
178 return_trans.set_rotation(return_trans.get_rotation() + (target_position * get_scale()).angle());
179 return return_trans;
180}
181
182bool Transform2D::operator==(const Transform2D &p_transform) const {
183 for (int i = 0; i < 3; i++) {
184 if (columns[i] != p_transform.columns[i]) {
185 return false;
186 }
187 }
188
189 return true;
190}
191
192bool Transform2D::operator!=(const Transform2D &p_transform) const {
193 for (int i = 0; i < 3; i++) {
194 if (columns[i] != p_transform.columns[i]) {
195 return true;
196 }
197 }
198
199 return false;
200}
201
202void Transform2D::operator*=(const Transform2D &p_transform) {
203 columns[2] = xform(p_transform.columns[2]);
204
205 real_t x0, x1, y0, y1;
206
207 x0 = tdotx(p_transform.columns[0]);
208 x1 = tdoty(p_transform.columns[0]);
209 y0 = tdotx(p_transform.columns[1]);
210 y1 = tdoty(p_transform.columns[1]);
211
212 columns[0][0] = x0;
213 columns[0][1] = x1;
214 columns[1][0] = y0;
215 columns[1][1] = y1;
216}
217
218Transform2D Transform2D::operator*(const Transform2D &p_transform) const {
219 Transform2D t = *this;
220 t *= p_transform;
221 return t;
222}
223
224Transform2D Transform2D::scaled(const Size2 &p_scale) const {
225 // Equivalent to left multiplication
226 Transform2D copy = *this;
227 copy.scale(p_scale);
228 return copy;
229}
230
231Transform2D Transform2D::scaled_local(const Size2 &p_scale) const {
232 // Equivalent to right multiplication
233 return Transform2D(columns[0] * p_scale.x, columns[1] * p_scale.y, columns[2]);
234}
235
236Transform2D Transform2D::untranslated() const {
237 Transform2D copy = *this;
238 copy.columns[2] = Vector2();
239 return copy;
240}
241
242Transform2D Transform2D::translated(const Vector2 &p_offset) const {
243 // Equivalent to left multiplication
244 return Transform2D(columns[0], columns[1], columns[2] + p_offset);
245}
246
247Transform2D Transform2D::translated_local(const Vector2 &p_offset) const {
248 // Equivalent to right multiplication
249 return Transform2D(columns[0], columns[1], columns[2] + basis_xform(p_offset));
250}
251
252Transform2D Transform2D::rotated(const real_t p_angle) const {
253 // Equivalent to left multiplication
254 return Transform2D(p_angle, Vector2()) * (*this);
255}
256
257Transform2D Transform2D::rotated_local(const real_t p_angle) const {
258 // Equivalent to right multiplication
259 return (*this) * Transform2D(p_angle, Vector2()); // Could be optimized, because origin transform can be skipped.
260}
261
262real_t Transform2D::determinant() const {
263 return columns[0].x * columns[1].y - columns[0].y * columns[1].x;
264}
265
266Transform2D Transform2D::interpolate_with(const Transform2D &p_transform, const real_t p_weight) const {
267 return Transform2D(
268 Math::lerp_angle(get_rotation(), p_transform.get_rotation(), p_weight),
269 get_scale().lerp(p_transform.get_scale(), p_weight),
270 Math::lerp_angle(get_skew(), p_transform.get_skew(), p_weight),
271 get_origin().lerp(p_transform.get_origin(), p_weight));
272}
273
274void Transform2D::operator*=(const real_t p_val) {
275 columns[0] *= p_val;
276 columns[1] *= p_val;
277 columns[2] *= p_val;
278}
279
280Transform2D Transform2D::operator*(const real_t p_val) const {
281 Transform2D ret(*this);
282 ret *= p_val;
283 return ret;
284}
285
286Transform2D::operator String() const {
287 return "[X: " + columns[0].operator String() +
288 ", Y: " + columns[1].operator String() +
289 ", O: " + columns[2].operator String() + "]";
290}
291