1/**************************************************************************/
2/* joint_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 "joint_2d.h"
32
33#include "physics_body_2d.h"
34#include "scene/scene_string_names.h"
35
36void Joint2D::_disconnect_signals() {
37 Node *node_a = get_node_or_null(a);
38 PhysicsBody2D *body_a = Object::cast_to<PhysicsBody2D>(node_a);
39 if (body_a) {
40 body_a->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Joint2D::_body_exit_tree));
41 }
42
43 Node *node_b = get_node_or_null(b);
44 PhysicsBody2D *body_b = Object::cast_to<PhysicsBody2D>(node_b);
45 if (body_b) {
46 body_b->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Joint2D::_body_exit_tree));
47 }
48}
49
50void Joint2D::_body_exit_tree() {
51 _disconnect_signals();
52 _update_joint(true);
53 update_configuration_warnings();
54}
55
56void Joint2D::_update_joint(bool p_only_free) {
57 if (ba.is_valid() && bb.is_valid() && exclude_from_collision) {
58 PhysicsServer2D::get_singleton()->joint_disable_collisions_between_bodies(joint, false);
59 }
60
61 ba = RID();
62 bb = RID();
63 configured = false;
64
65 if (p_only_free || !is_inside_tree()) {
66 PhysicsServer2D::get_singleton()->joint_clear(joint);
67 warning = String();
68 return;
69 }
70
71 Node *node_a = get_node_or_null(a);
72 Node *node_b = get_node_or_null(b);
73
74 PhysicsBody2D *body_a = Object::cast_to<PhysicsBody2D>(node_a);
75 PhysicsBody2D *body_b = Object::cast_to<PhysicsBody2D>(node_b);
76
77 bool valid = false;
78
79 if (node_a && !body_a && node_b && !body_b) {
80 warning = RTR("Node A and Node B must be PhysicsBody2Ds");
81 } else if (node_a && !body_a) {
82 warning = RTR("Node A must be a PhysicsBody2D");
83 } else if (node_b && !body_b) {
84 warning = RTR("Node B must be a PhysicsBody2D");
85 } else if (!body_a || !body_b) {
86 warning = RTR("Joint is not connected to two PhysicsBody2Ds");
87 } else if (body_a == body_b) {
88 warning = RTR("Node A and Node B must be different PhysicsBody2Ds");
89 } else {
90 warning = String();
91 valid = true;
92 }
93
94 update_configuration_warnings();
95
96 if (!valid) {
97 PhysicsServer2D::get_singleton()->joint_clear(joint);
98 return;
99 }
100
101 if (body_a) {
102 body_a->force_update_transform();
103 }
104
105 if (body_b) {
106 body_b->force_update_transform();
107 }
108
109 configured = true;
110
111 _configure_joint(joint, body_a, body_b);
112
113 ERR_FAIL_COND_MSG(!joint.is_valid(), "Failed to configure the joint.");
114
115 PhysicsServer2D::get_singleton()->joint_set_param(joint, PhysicsServer2D::JOINT_PARAM_BIAS, bias);
116
117 ba = body_a->get_rid();
118 bb = body_b->get_rid();
119
120 body_a->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Joint2D::_body_exit_tree));
121 body_b->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Joint2D::_body_exit_tree));
122
123 PhysicsServer2D::get_singleton()->joint_disable_collisions_between_bodies(joint, exclude_from_collision);
124}
125
126void Joint2D::set_node_a(const NodePath &p_node_a) {
127 if (a == p_node_a) {
128 return;
129 }
130
131 if (is_configured()) {
132 _disconnect_signals();
133 }
134
135 a = p_node_a;
136 if (Engine::get_singleton()->is_editor_hint()) {
137 // When in editor, the setter may be called as a result of node rename.
138 // It happens before the node actually changes its name, which triggers false warning.
139 callable_mp(this, &Joint2D::_update_joint).call_deferred();
140 } else {
141 _update_joint();
142 }
143}
144
145NodePath Joint2D::get_node_a() const {
146 return a;
147}
148
149void Joint2D::set_node_b(const NodePath &p_node_b) {
150 if (b == p_node_b) {
151 return;
152 }
153
154 if (is_configured()) {
155 _disconnect_signals();
156 }
157
158 b = p_node_b;
159 if (Engine::get_singleton()->is_editor_hint()) {
160 callable_mp(this, &Joint2D::_update_joint).call_deferred();
161 } else {
162 _update_joint();
163 }
164}
165
166NodePath Joint2D::get_node_b() const {
167 return b;
168}
169
170void Joint2D::_notification(int p_what) {
171 switch (p_what) {
172 case NOTIFICATION_POST_ENTER_TREE: {
173 if (is_configured()) {
174 _disconnect_signals();
175 }
176 _update_joint();
177 } break;
178
179 case NOTIFICATION_EXIT_TREE: {
180 if (is_configured()) {
181 _disconnect_signals();
182 }
183 _update_joint(true);
184 } break;
185 }
186}
187
188void Joint2D::set_bias(real_t p_bias) {
189 bias = p_bias;
190 if (joint.is_valid()) {
191 PhysicsServer2D::get_singleton()->joint_set_param(joint, PhysicsServer2D::JOINT_PARAM_BIAS, bias);
192 }
193}
194
195real_t Joint2D::get_bias() const {
196 return bias;
197}
198
199void Joint2D::set_exclude_nodes_from_collision(bool p_enable) {
200 if (exclude_from_collision == p_enable) {
201 return;
202 }
203 if (is_configured()) {
204 _disconnect_signals();
205 }
206 _update_joint(true);
207 exclude_from_collision = p_enable;
208 _update_joint();
209}
210
211bool Joint2D::get_exclude_nodes_from_collision() const {
212 return exclude_from_collision;
213}
214
215PackedStringArray Joint2D::get_configuration_warnings() const {
216 PackedStringArray warnings = Node2D::get_configuration_warnings();
217
218 if (!warning.is_empty()) {
219 warnings.push_back(warning);
220 }
221
222 return warnings;
223}
224
225void Joint2D::_bind_methods() {
226 ClassDB::bind_method(D_METHOD("set_node_a", "node"), &Joint2D::set_node_a);
227 ClassDB::bind_method(D_METHOD("get_node_a"), &Joint2D::get_node_a);
228
229 ClassDB::bind_method(D_METHOD("set_node_b", "node"), &Joint2D::set_node_b);
230 ClassDB::bind_method(D_METHOD("get_node_b"), &Joint2D::get_node_b);
231
232 ClassDB::bind_method(D_METHOD("set_bias", "bias"), &Joint2D::set_bias);
233 ClassDB::bind_method(D_METHOD("get_bias"), &Joint2D::get_bias);
234
235 ClassDB::bind_method(D_METHOD("set_exclude_nodes_from_collision", "enable"), &Joint2D::set_exclude_nodes_from_collision);
236 ClassDB::bind_method(D_METHOD("get_exclude_nodes_from_collision"), &Joint2D::get_exclude_nodes_from_collision);
237
238 ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "node_a", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "PhysicsBody2D"), "set_node_a", "get_node_a");
239 ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "node_b", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "PhysicsBody2D"), "set_node_b", "get_node_b");
240 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bias", PROPERTY_HINT_RANGE, "0,0.9,0.001"), "set_bias", "get_bias");
241 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_collision"), "set_exclude_nodes_from_collision", "get_exclude_nodes_from_collision");
242}
243
244Joint2D::Joint2D() {
245 joint = PhysicsServer2D::get_singleton()->joint_create();
246 set_hide_clip_children(true);
247}
248
249Joint2D::~Joint2D() {
250 ERR_FAIL_NULL(PhysicsServer2D::get_singleton());
251 PhysicsServer2D::get_singleton()->free(joint);
252}
253
254///////////////////////////////////////////////////////////////////////////////
255///////////////////////////////////////////////////////////////////////////////
256///////////////////////////////////////////////////////////////////////////////
257
258void PinJoint2D::_notification(int p_what) {
259 switch (p_what) {
260 case NOTIFICATION_DRAW: {
261 if (!is_inside_tree()) {
262 break;
263 }
264
265 if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) {
266 break;
267 }
268
269 draw_line(Point2(-10, 0), Point2(+10, 0), Color(0.7, 0.6, 0.0, 0.5), 3);
270 draw_line(Point2(0, -10), Point2(0, +10), Color(0.7, 0.6, 0.0, 0.5), 3);
271 } break;
272 }
273}
274
275void PinJoint2D::_configure_joint(RID p_joint, PhysicsBody2D *body_a, PhysicsBody2D *body_b) {
276 PhysicsServer2D::get_singleton()->joint_make_pin(p_joint, get_global_position(), body_a->get_rid(), body_b ? body_b->get_rid() : RID());
277 PhysicsServer2D::get_singleton()->pin_joint_set_param(p_joint, PhysicsServer2D::PIN_JOINT_SOFTNESS, softness);
278}
279
280void PinJoint2D::set_softness(real_t p_softness) {
281 softness = p_softness;
282 queue_redraw();
283 if (is_configured()) {
284 PhysicsServer2D::get_singleton()->pin_joint_set_param(get_joint(), PhysicsServer2D::PIN_JOINT_SOFTNESS, p_softness);
285 }
286}
287
288real_t PinJoint2D::get_softness() const {
289 return softness;
290}
291
292void PinJoint2D::_bind_methods() {
293 ClassDB::bind_method(D_METHOD("set_softness", "softness"), &PinJoint2D::set_softness);
294 ClassDB::bind_method(D_METHOD("get_softness"), &PinJoint2D::get_softness);
295
296 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "softness", PROPERTY_HINT_RANGE, "0.00,16,0.01,exp"), "set_softness", "get_softness");
297}
298
299PinJoint2D::PinJoint2D() {
300}
301
302///////////////////////////////////////////////////////////////////////////////
303///////////////////////////////////////////////////////////////////////////////
304///////////////////////////////////////////////////////////////////////////////
305
306void GrooveJoint2D::_notification(int p_what) {
307 switch (p_what) {
308 case NOTIFICATION_DRAW: {
309 if (!is_inside_tree()) {
310 break;
311 }
312
313 if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) {
314 break;
315 }
316
317 draw_line(Point2(-10, 0), Point2(+10, 0), Color(0.7, 0.6, 0.0, 0.5), 3);
318 draw_line(Point2(-10, length), Point2(+10, length), Color(0.7, 0.6, 0.0, 0.5), 3);
319 draw_line(Point2(0, 0), Point2(0, length), Color(0.7, 0.6, 0.0, 0.5), 3);
320 draw_line(Point2(-10, initial_offset), Point2(+10, initial_offset), Color(0.8, 0.8, 0.9, 0.5), 5);
321 } break;
322 }
323}
324
325void GrooveJoint2D::_configure_joint(RID p_joint, PhysicsBody2D *body_a, PhysicsBody2D *body_b) {
326 Transform2D gt = get_global_transform();
327 Vector2 groove_A1 = gt.get_origin();
328 Vector2 groove_A2 = gt.xform(Vector2(0, length));
329 Vector2 anchor_B = gt.xform(Vector2(0, initial_offset));
330
331 PhysicsServer2D::get_singleton()->joint_make_groove(p_joint, groove_A1, groove_A2, anchor_B, body_a->get_rid(), body_b->get_rid());
332}
333
334void GrooveJoint2D::set_length(real_t p_length) {
335 length = p_length;
336 queue_redraw();
337}
338
339real_t GrooveJoint2D::get_length() const {
340 return length;
341}
342
343void GrooveJoint2D::set_initial_offset(real_t p_initial_offset) {
344 initial_offset = p_initial_offset;
345 queue_redraw();
346}
347
348real_t GrooveJoint2D::get_initial_offset() const {
349 return initial_offset;
350}
351
352void GrooveJoint2D::_bind_methods() {
353 ClassDB::bind_method(D_METHOD("set_length", "length"), &GrooveJoint2D::set_length);
354 ClassDB::bind_method(D_METHOD("get_length"), &GrooveJoint2D::get_length);
355 ClassDB::bind_method(D_METHOD("set_initial_offset", "offset"), &GrooveJoint2D::set_initial_offset);
356 ClassDB::bind_method(D_METHOD("get_initial_offset"), &GrooveJoint2D::get_initial_offset);
357
358 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "1,65535,1,exp,suffix:px"), "set_length", "get_length");
359 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "initial_offset", PROPERTY_HINT_RANGE, "1,65535,1,exp,suffix:px"), "set_initial_offset", "get_initial_offset");
360}
361
362GrooveJoint2D::GrooveJoint2D() {
363}
364
365///////////////////////////////////////////////////////////////////////////////
366///////////////////////////////////////////////////////////////////////////////
367///////////////////////////////////////////////////////////////////////////////
368
369void DampedSpringJoint2D::_notification(int p_what) {
370 switch (p_what) {
371 case NOTIFICATION_DRAW: {
372 if (!is_inside_tree()) {
373 break;
374 }
375
376 if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) {
377 break;
378 }
379
380 draw_line(Point2(-10, 0), Point2(+10, 0), Color(0.7, 0.6, 0.0, 0.5), 3);
381 draw_line(Point2(-10, length), Point2(+10, length), Color(0.7, 0.6, 0.0, 0.5), 3);
382 draw_line(Point2(0, 0), Point2(0, length), Color(0.7, 0.6, 0.0, 0.5), 3);
383 } break;
384 }
385}
386
387void DampedSpringJoint2D::_configure_joint(RID p_joint, PhysicsBody2D *body_a, PhysicsBody2D *body_b) {
388 Transform2D gt = get_global_transform();
389 Vector2 anchor_A = gt.get_origin();
390 Vector2 anchor_B = gt.xform(Vector2(0, length));
391
392 PhysicsServer2D::get_singleton()->joint_make_damped_spring(p_joint, anchor_A, anchor_B, body_a->get_rid(), body_b->get_rid());
393 if (rest_length) {
394 PhysicsServer2D::get_singleton()->damped_spring_joint_set_param(p_joint, PhysicsServer2D::DAMPED_SPRING_REST_LENGTH, rest_length);
395 }
396 PhysicsServer2D::get_singleton()->damped_spring_joint_set_param(p_joint, PhysicsServer2D::DAMPED_SPRING_STIFFNESS, stiffness);
397 PhysicsServer2D::get_singleton()->damped_spring_joint_set_param(p_joint, PhysicsServer2D::DAMPED_SPRING_DAMPING, damping);
398}
399
400void DampedSpringJoint2D::set_length(real_t p_length) {
401 length = p_length;
402 queue_redraw();
403}
404
405real_t DampedSpringJoint2D::get_length() const {
406 return length;
407}
408
409void DampedSpringJoint2D::set_rest_length(real_t p_rest_length) {
410 rest_length = p_rest_length;
411 queue_redraw();
412 if (is_configured()) {
413 PhysicsServer2D::get_singleton()->damped_spring_joint_set_param(get_joint(), PhysicsServer2D::DAMPED_SPRING_REST_LENGTH, p_rest_length ? p_rest_length : length);
414 }
415}
416
417real_t DampedSpringJoint2D::get_rest_length() const {
418 return rest_length;
419}
420
421void DampedSpringJoint2D::set_stiffness(real_t p_stiffness) {
422 stiffness = p_stiffness;
423 queue_redraw();
424 if (is_configured()) {
425 PhysicsServer2D::get_singleton()->damped_spring_joint_set_param(get_joint(), PhysicsServer2D::DAMPED_SPRING_STIFFNESS, p_stiffness);
426 }
427}
428
429real_t DampedSpringJoint2D::get_stiffness() const {
430 return stiffness;
431}
432
433void DampedSpringJoint2D::set_damping(real_t p_damping) {
434 damping = p_damping;
435 queue_redraw();
436 if (is_configured()) {
437 PhysicsServer2D::get_singleton()->damped_spring_joint_set_param(get_joint(), PhysicsServer2D::DAMPED_SPRING_DAMPING, p_damping);
438 }
439}
440
441real_t DampedSpringJoint2D::get_damping() const {
442 return damping;
443}
444
445void DampedSpringJoint2D::_bind_methods() {
446 ClassDB::bind_method(D_METHOD("set_length", "length"), &DampedSpringJoint2D::set_length);
447 ClassDB::bind_method(D_METHOD("get_length"), &DampedSpringJoint2D::get_length);
448 ClassDB::bind_method(D_METHOD("set_rest_length", "rest_length"), &DampedSpringJoint2D::set_rest_length);
449 ClassDB::bind_method(D_METHOD("get_rest_length"), &DampedSpringJoint2D::get_rest_length);
450 ClassDB::bind_method(D_METHOD("set_stiffness", "stiffness"), &DampedSpringJoint2D::set_stiffness);
451 ClassDB::bind_method(D_METHOD("get_stiffness"), &DampedSpringJoint2D::get_stiffness);
452 ClassDB::bind_method(D_METHOD("set_damping", "damping"), &DampedSpringJoint2D::set_damping);
453 ClassDB::bind_method(D_METHOD("get_damping"), &DampedSpringJoint2D::get_damping);
454
455 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "1,65535,1,exp,suffix:px"), "set_length", "get_length");
456 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rest_length", PROPERTY_HINT_RANGE, "0,65535,1,exp,suffix:px"), "set_rest_length", "get_rest_length");
457 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "stiffness", PROPERTY_HINT_RANGE, "0.1,64,0.1,exp"), "set_stiffness", "get_stiffness");
458 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping", PROPERTY_HINT_RANGE, "0.01,16,0.01,exp"), "set_damping", "get_damping");
459}
460
461DampedSpringJoint2D::DampedSpringJoint2D() {
462}
463