1/**************************************************************************/
2/* node_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 "node_2d.h"
32
33#include "scene/main/viewport.h"
34
35#ifdef TOOLS_ENABLED
36Dictionary Node2D::_edit_get_state() const {
37 Dictionary state;
38 state["position"] = get_position();
39 state["rotation"] = get_rotation();
40 state["scale"] = get_scale();
41 state["skew"] = get_skew();
42
43 return state;
44}
45
46void Node2D::_edit_set_state(const Dictionary &p_state) {
47 position = p_state["position"];
48 rotation = p_state["rotation"];
49 scale = p_state["scale"];
50 skew = p_state["skew"];
51
52 _update_transform();
53}
54
55void Node2D::_edit_set_position(const Point2 &p_position) {
56 set_position(p_position);
57}
58
59Point2 Node2D::_edit_get_position() const {
60 return position;
61}
62
63void Node2D::_edit_set_scale(const Size2 &p_scale) {
64 set_scale(p_scale);
65}
66
67Size2 Node2D::_edit_get_scale() const {
68 return scale;
69}
70
71void Node2D::_edit_set_rotation(real_t p_rotation) {
72 rotation = p_rotation;
73 _update_transform();
74}
75
76real_t Node2D::_edit_get_rotation() const {
77 return rotation;
78}
79
80bool Node2D::_edit_use_rotation() const {
81 return true;
82}
83
84void Node2D::_edit_set_rect(const Rect2 &p_edit_rect) {
85 ERR_FAIL_COND(!_edit_use_rect());
86
87 Rect2 r = _edit_get_rect();
88
89 Vector2 zero_offset;
90 Size2 new_scale(1, 1);
91
92 if (r.size.x != 0) {
93 zero_offset.x = -r.position.x / r.size.x;
94 new_scale.x = p_edit_rect.size.x / r.size.x;
95 }
96
97 if (r.size.y != 0) {
98 zero_offset.y = -r.position.y / r.size.y;
99 new_scale.y = p_edit_rect.size.y / r.size.y;
100 }
101
102 Point2 new_pos = p_edit_rect.position + p_edit_rect.size * zero_offset;
103
104 Transform2D postxf;
105 postxf.set_rotation_scale_and_skew(rotation, scale, skew);
106 new_pos = postxf.xform(new_pos);
107
108 position += new_pos;
109 scale *= new_scale;
110
111 _update_transform();
112}
113#endif
114
115void Node2D::_set_xform_dirty(bool p_dirty) const {
116 if (is_group_processing()) {
117 if (p_dirty) {
118 xform_dirty.mt.set();
119 } else {
120 xform_dirty.mt.clear();
121 }
122 } else {
123 xform_dirty.st = p_dirty;
124 }
125}
126
127void Node2D::_update_xform_values() const {
128 rotation = transform.get_rotation();
129 skew = transform.get_skew();
130 position = transform.columns[2];
131 scale = transform.get_scale();
132 _set_xform_dirty(false);
133}
134
135void Node2D::_update_transform() {
136 transform.set_rotation_scale_and_skew(rotation, scale, skew);
137 transform.columns[2] = position;
138
139 RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), transform);
140
141 _notify_transform();
142}
143
144void Node2D::reparent(Node *p_parent, bool p_keep_global_transform) {
145 ERR_THREAD_GUARD;
146 Transform2D temp = get_global_transform();
147 Node::reparent(p_parent);
148 if (p_keep_global_transform) {
149 set_global_transform(temp);
150 }
151}
152
153void Node2D::set_position(const Point2 &p_pos) {
154 ERR_THREAD_GUARD;
155 if (_is_xform_dirty()) {
156 _update_xform_values();
157 }
158 position = p_pos;
159 _update_transform();
160}
161
162void Node2D::set_rotation(real_t p_radians) {
163 ERR_THREAD_GUARD;
164 if (_is_xform_dirty()) {
165 _update_xform_values();
166 }
167 rotation = p_radians;
168 _update_transform();
169}
170
171void Node2D::set_rotation_degrees(real_t p_degrees) {
172 ERR_THREAD_GUARD;
173 set_rotation(Math::deg_to_rad(p_degrees));
174}
175
176void Node2D::set_skew(real_t p_radians) {
177 ERR_THREAD_GUARD;
178 if (_is_xform_dirty()) {
179 _update_xform_values();
180 }
181 skew = p_radians;
182 _update_transform();
183}
184
185void Node2D::set_scale(const Size2 &p_scale) {
186 ERR_THREAD_GUARD;
187 if (_is_xform_dirty()) {
188 _update_xform_values();
189 }
190 scale = p_scale;
191 // Avoid having 0 scale values, can lead to errors in physics and rendering.
192 if (Math::is_zero_approx(scale.x)) {
193 scale.x = CMP_EPSILON;
194 }
195 if (Math::is_zero_approx(scale.y)) {
196 scale.y = CMP_EPSILON;
197 }
198 _update_transform();
199}
200
201Point2 Node2D::get_position() const {
202 ERR_READ_THREAD_GUARD_V(Point2());
203 if (_is_xform_dirty()) {
204 _update_xform_values();
205 }
206
207 return position;
208}
209
210real_t Node2D::get_rotation() const {
211 ERR_READ_THREAD_GUARD_V(0);
212 if (_is_xform_dirty()) {
213 _update_xform_values();
214 }
215
216 return rotation;
217}
218
219real_t Node2D::get_rotation_degrees() const {
220 ERR_READ_THREAD_GUARD_V(0);
221 return Math::rad_to_deg(get_rotation());
222}
223
224real_t Node2D::get_skew() const {
225 ERR_READ_THREAD_GUARD_V(0);
226 if (_is_xform_dirty()) {
227 _update_xform_values();
228 }
229
230 return skew;
231}
232
233Size2 Node2D::get_scale() const {
234 ERR_READ_THREAD_GUARD_V(Size2());
235 if (_is_xform_dirty()) {
236 _update_xform_values();
237 }
238
239 return scale;
240}
241
242Transform2D Node2D::get_transform() const {
243 ERR_READ_THREAD_GUARD_V(Transform2D());
244 return transform;
245}
246
247void Node2D::rotate(real_t p_radians) {
248 ERR_THREAD_GUARD;
249 set_rotation(get_rotation() + p_radians);
250}
251
252void Node2D::translate(const Vector2 &p_amount) {
253 ERR_THREAD_GUARD;
254 set_position(get_position() + p_amount);
255}
256
257void Node2D::global_translate(const Vector2 &p_amount) {
258 ERR_THREAD_GUARD;
259 set_global_position(get_global_position() + p_amount);
260}
261
262void Node2D::apply_scale(const Size2 &p_amount) {
263 ERR_THREAD_GUARD;
264 set_scale(get_scale() * p_amount);
265}
266
267void Node2D::move_x(real_t p_delta, bool p_scaled) {
268 ERR_THREAD_GUARD;
269 Transform2D t = get_transform();
270 Vector2 m = t[0];
271 if (!p_scaled) {
272 m.normalize();
273 }
274 set_position(t[2] + m * p_delta);
275}
276
277void Node2D::move_y(real_t p_delta, bool p_scaled) {
278 ERR_THREAD_GUARD;
279 Transform2D t = get_transform();
280 Vector2 m = t[1];
281 if (!p_scaled) {
282 m.normalize();
283 }
284 set_position(t[2] + m * p_delta);
285}
286
287Point2 Node2D::get_global_position() const {
288 ERR_READ_THREAD_GUARD_V(Point2());
289 return get_global_transform().get_origin();
290}
291
292void Node2D::set_global_position(const Point2 &p_pos) {
293 ERR_THREAD_GUARD;
294 CanvasItem *parent = get_parent_item();
295 if (parent) {
296 Transform2D inv = parent->get_global_transform().affine_inverse();
297 set_position(inv.xform(p_pos));
298 } else {
299 set_position(p_pos);
300 }
301}
302
303real_t Node2D::get_global_rotation() const {
304 ERR_READ_THREAD_GUARD_V(0);
305 return get_global_transform().get_rotation();
306}
307
308real_t Node2D::get_global_rotation_degrees() const {
309 ERR_READ_THREAD_GUARD_V(0);
310 return Math::rad_to_deg(get_global_rotation());
311}
312
313real_t Node2D::get_global_skew() const {
314 ERR_READ_THREAD_GUARD_V(0);
315 return get_global_transform().get_skew();
316}
317
318void Node2D::set_global_rotation(const real_t p_radians) {
319 ERR_THREAD_GUARD;
320 CanvasItem *parent = get_parent_item();
321 if (parent) {
322 Transform2D parent_global_transform = parent->get_global_transform();
323 Transform2D new_transform = parent_global_transform * get_transform();
324 new_transform.set_rotation(p_radians);
325 new_transform = parent_global_transform.affine_inverse() * new_transform;
326 set_rotation(new_transform.get_rotation());
327 } else {
328 set_rotation(p_radians);
329 }
330}
331
332void Node2D::set_global_rotation_degrees(const real_t p_degrees) {
333 ERR_THREAD_GUARD;
334 set_global_rotation(Math::deg_to_rad(p_degrees));
335}
336
337void Node2D::set_global_skew(const real_t p_radians) {
338 ERR_THREAD_GUARD;
339 CanvasItem *parent = get_parent_item();
340 if (parent) {
341 Transform2D parent_global_transform = parent->get_global_transform();
342 Transform2D new_transform = parent_global_transform * get_transform();
343 new_transform.set_skew(p_radians);
344 new_transform = parent_global_transform.affine_inverse() * new_transform;
345 set_skew(new_transform.get_skew());
346 } else {
347 set_skew(p_radians);
348 }
349}
350
351Size2 Node2D::get_global_scale() const {
352 ERR_READ_THREAD_GUARD_V(Size2());
353 return get_global_transform().get_scale();
354}
355
356void Node2D::set_global_scale(const Size2 &p_scale) {
357 ERR_THREAD_GUARD;
358 CanvasItem *parent = get_parent_item();
359 if (parent) {
360 Transform2D parent_global_transform = parent->get_global_transform();
361 Transform2D new_transform = parent_global_transform * get_transform();
362 new_transform.set_scale(p_scale);
363 new_transform = parent_global_transform.affine_inverse() * new_transform;
364 set_scale(new_transform.get_scale());
365 } else {
366 set_scale(p_scale);
367 }
368}
369
370void Node2D::set_transform(const Transform2D &p_transform) {
371 ERR_THREAD_GUARD;
372 transform = p_transform;
373 _set_xform_dirty(true);
374
375 RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), transform);
376
377 _notify_transform();
378}
379
380void Node2D::set_global_transform(const Transform2D &p_transform) {
381 ERR_THREAD_GUARD;
382 CanvasItem *parent = get_parent_item();
383 if (parent) {
384 set_transform(parent->get_global_transform().affine_inverse() * p_transform);
385 } else {
386 set_transform(p_transform);
387 }
388}
389
390Transform2D Node2D::get_relative_transform_to_parent(const Node *p_parent) const {
391 ERR_READ_THREAD_GUARD_V(Transform2D());
392 if (p_parent == this) {
393 return Transform2D();
394 }
395
396 Node2D *parent_2d = Object::cast_to<Node2D>(get_parent());
397
398 ERR_FAIL_NULL_V(parent_2d, Transform2D());
399 if (p_parent == parent_2d) {
400 return get_transform();
401 } else {
402 return parent_2d->get_relative_transform_to_parent(p_parent) * get_transform();
403 }
404}
405
406void Node2D::look_at(const Vector2 &p_pos) {
407 ERR_THREAD_GUARD;
408 rotate(get_angle_to(p_pos));
409}
410
411real_t Node2D::get_angle_to(const Vector2 &p_pos) const {
412 ERR_READ_THREAD_GUARD_V(0);
413 return (to_local(p_pos) * get_scale()).angle();
414}
415
416Point2 Node2D::to_local(Point2 p_global) const {
417 ERR_READ_THREAD_GUARD_V(Point2());
418 return get_global_transform().affine_inverse().xform(p_global);
419}
420
421Point2 Node2D::to_global(Point2 p_local) const {
422 ERR_READ_THREAD_GUARD_V(Point2());
423 return get_global_transform().xform(p_local);
424}
425
426void Node2D::_notification(int p_notification) {
427 ERR_THREAD_GUARD;
428 switch (p_notification) {
429 case NOTIFICATION_ENTER_TREE: {
430 if (get_viewport()) {
431 get_parent()->connect(SNAME("child_order_changed"), callable_mp(get_viewport(), &Viewport::gui_set_root_order_dirty), CONNECT_REFERENCE_COUNTED);
432 }
433 } break;
434 case NOTIFICATION_EXIT_TREE: {
435 if (get_viewport()) {
436 get_parent()->disconnect(SNAME("child_order_changed"), callable_mp(get_viewport(), &Viewport::gui_set_root_order_dirty));
437 }
438 } break;
439 }
440}
441
442void Node2D::_bind_methods() {
443 ClassDB::bind_method(D_METHOD("set_position", "position"), &Node2D::set_position);
444 ClassDB::bind_method(D_METHOD("set_rotation", "radians"), &Node2D::set_rotation);
445 ClassDB::bind_method(D_METHOD("set_rotation_degrees", "degrees"), &Node2D::set_rotation_degrees);
446 ClassDB::bind_method(D_METHOD("set_skew", "radians"), &Node2D::set_skew);
447 ClassDB::bind_method(D_METHOD("set_scale", "scale"), &Node2D::set_scale);
448
449 ClassDB::bind_method(D_METHOD("get_position"), &Node2D::get_position);
450 ClassDB::bind_method(D_METHOD("get_rotation"), &Node2D::get_rotation);
451 ClassDB::bind_method(D_METHOD("get_rotation_degrees"), &Node2D::get_rotation_degrees);
452 ClassDB::bind_method(D_METHOD("get_skew"), &Node2D::get_skew);
453 ClassDB::bind_method(D_METHOD("get_scale"), &Node2D::get_scale);
454
455 ClassDB::bind_method(D_METHOD("rotate", "radians"), &Node2D::rotate);
456 ClassDB::bind_method(D_METHOD("move_local_x", "delta", "scaled"), &Node2D::move_x, DEFVAL(false));
457 ClassDB::bind_method(D_METHOD("move_local_y", "delta", "scaled"), &Node2D::move_y, DEFVAL(false));
458 ClassDB::bind_method(D_METHOD("translate", "offset"), &Node2D::translate);
459 ClassDB::bind_method(D_METHOD("global_translate", "offset"), &Node2D::global_translate);
460 ClassDB::bind_method(D_METHOD("apply_scale", "ratio"), &Node2D::apply_scale);
461
462 ClassDB::bind_method(D_METHOD("set_global_position", "position"), &Node2D::set_global_position);
463 ClassDB::bind_method(D_METHOD("get_global_position"), &Node2D::get_global_position);
464 ClassDB::bind_method(D_METHOD("set_global_rotation", "radians"), &Node2D::set_global_rotation);
465 ClassDB::bind_method(D_METHOD("set_global_rotation_degrees", "degrees"), &Node2D::set_global_rotation_degrees);
466 ClassDB::bind_method(D_METHOD("get_global_rotation"), &Node2D::get_global_rotation);
467 ClassDB::bind_method(D_METHOD("get_global_rotation_degrees"), &Node2D::get_global_rotation_degrees);
468 ClassDB::bind_method(D_METHOD("set_global_skew", "radians"), &Node2D::set_global_skew);
469 ClassDB::bind_method(D_METHOD("get_global_skew"), &Node2D::get_global_skew);
470 ClassDB::bind_method(D_METHOD("set_global_scale", "scale"), &Node2D::set_global_scale);
471 ClassDB::bind_method(D_METHOD("get_global_scale"), &Node2D::get_global_scale);
472
473 ClassDB::bind_method(D_METHOD("set_transform", "xform"), &Node2D::set_transform);
474 ClassDB::bind_method(D_METHOD("set_global_transform", "xform"), &Node2D::set_global_transform);
475
476 ClassDB::bind_method(D_METHOD("look_at", "point"), &Node2D::look_at);
477 ClassDB::bind_method(D_METHOD("get_angle_to", "point"), &Node2D::get_angle_to);
478
479 ClassDB::bind_method(D_METHOD("to_local", "global_point"), &Node2D::to_local);
480 ClassDB::bind_method(D_METHOD("to_global", "local_point"), &Node2D::to_global);
481
482 ClassDB::bind_method(D_METHOD("get_relative_transform_to_parent", "parent"), &Node2D::get_relative_transform_to_parent);
483
484 ADD_GROUP("Transform", "");
485 ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_RANGE, "-99999,99999,0.001,or_less,or_greater,hide_slider,suffix:px"), "set_position", "get_position");
486 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_less,or_greater,radians"), "set_rotation", "get_rotation");
487 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation_degrees", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_rotation_degrees", "get_rotation_degrees");
488 ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scale", PROPERTY_HINT_LINK), "set_scale", "get_scale");
489 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "skew", PROPERTY_HINT_RANGE, "-89.9,89.9,0.1,radians"), "set_skew", "get_skew");
490 ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "transform", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_NONE), "set_transform", "get_transform");
491
492 ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "global_position", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_NONE), "set_global_position", "get_global_position");
493 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "global_rotation", PROPERTY_HINT_NONE, "radians", PROPERTY_USAGE_NONE), "set_global_rotation", "get_global_rotation");
494 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "global_rotation_degrees", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_rotation_degrees", "get_global_rotation_degrees");
495 ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "global_scale", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_scale", "get_global_scale");
496 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "global_skew", PROPERTY_HINT_NONE, "radians", PROPERTY_USAGE_NONE), "set_global_skew", "get_global_skew");
497 ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "global_transform", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_NONE), "set_global_transform", "get_global_transform");
498}
499