1 | /**************************************************************************/ |
2 | /* skeleton_modification_2d_lookat.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 "skeleton_modification_2d_lookat.h" |
32 | #include "scene/2d/skeleton_2d.h" |
33 | |
34 | #ifdef TOOLS_ENABLED |
35 | #include "editor/editor_settings.h" |
36 | #endif // TOOLS_ENABLED |
37 | |
38 | bool SkeletonModification2DLookAt::_set(const StringName &p_path, const Variant &p_value) { |
39 | String path = p_path; |
40 | |
41 | if (path.begins_with("enable_constraint" )) { |
42 | set_enable_constraint(p_value); |
43 | } else if (path.begins_with("constraint_angle_min" )) { |
44 | set_constraint_angle_min(Math::deg_to_rad(float(p_value))); |
45 | } else if (path.begins_with("constraint_angle_max" )) { |
46 | set_constraint_angle_max(Math::deg_to_rad(float(p_value))); |
47 | } else if (path.begins_with("constraint_angle_invert" )) { |
48 | set_constraint_angle_invert(p_value); |
49 | } else if (path.begins_with("constraint_in_localspace" )) { |
50 | set_constraint_in_localspace(p_value); |
51 | } else if (path.begins_with("additional_rotation" )) { |
52 | set_additional_rotation(Math::deg_to_rad(float(p_value))); |
53 | } |
54 | |
55 | #ifdef TOOLS_ENABLED |
56 | if (path.begins_with("editor/draw_gizmo" )) { |
57 | set_editor_draw_gizmo(p_value); |
58 | } |
59 | #endif // TOOLS_ENABLED |
60 | |
61 | return true; |
62 | } |
63 | |
64 | bool SkeletonModification2DLookAt::_get(const StringName &p_path, Variant &r_ret) const { |
65 | String path = p_path; |
66 | |
67 | if (path.begins_with("enable_constraint" )) { |
68 | r_ret = get_enable_constraint(); |
69 | } else if (path.begins_with("constraint_angle_min" )) { |
70 | r_ret = Math::rad_to_deg(get_constraint_angle_min()); |
71 | } else if (path.begins_with("constraint_angle_max" )) { |
72 | r_ret = Math::rad_to_deg(get_constraint_angle_max()); |
73 | } else if (path.begins_with("constraint_angle_invert" )) { |
74 | r_ret = get_constraint_angle_invert(); |
75 | } else if (path.begins_with("constraint_in_localspace" )) { |
76 | r_ret = get_constraint_in_localspace(); |
77 | } else if (path.begins_with("additional_rotation" )) { |
78 | r_ret = Math::rad_to_deg(get_additional_rotation()); |
79 | } |
80 | |
81 | #ifdef TOOLS_ENABLED |
82 | if (path.begins_with("editor/draw_gizmo" )) { |
83 | r_ret = get_editor_draw_gizmo(); |
84 | } |
85 | #endif // TOOLS_ENABLED |
86 | |
87 | return true; |
88 | } |
89 | |
90 | void SkeletonModification2DLookAt::_get_property_list(List<PropertyInfo> *p_list) const { |
91 | p_list->push_back(PropertyInfo(Variant::BOOL, "enable_constraint" , PROPERTY_HINT_NONE, "" , PROPERTY_USAGE_DEFAULT)); |
92 | if (enable_constraint) { |
93 | p_list->push_back(PropertyInfo(Variant::FLOAT, "constraint_angle_min" , PROPERTY_HINT_RANGE, "-360, 360, 0.01" , PROPERTY_USAGE_DEFAULT)); |
94 | p_list->push_back(PropertyInfo(Variant::FLOAT, "constraint_angle_max" , PROPERTY_HINT_RANGE, "-360, 360, 0.01" , PROPERTY_USAGE_DEFAULT)); |
95 | p_list->push_back(PropertyInfo(Variant::BOOL, "constraint_angle_invert" , PROPERTY_HINT_NONE, "" , PROPERTY_USAGE_DEFAULT)); |
96 | p_list->push_back(PropertyInfo(Variant::BOOL, "constraint_in_localspace" , PROPERTY_HINT_NONE, "" , PROPERTY_USAGE_DEFAULT)); |
97 | } |
98 | p_list->push_back(PropertyInfo(Variant::FLOAT, "additional_rotation" , PROPERTY_HINT_NONE, "" , PROPERTY_USAGE_DEFAULT)); |
99 | |
100 | #ifdef TOOLS_ENABLED |
101 | if (Engine::get_singleton()->is_editor_hint()) { |
102 | p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_gizmo" , PROPERTY_HINT_NONE, "" , PROPERTY_USAGE_DEFAULT)); |
103 | } |
104 | #endif // TOOLS_ENABLED |
105 | } |
106 | |
107 | void SkeletonModification2DLookAt::_execute(float p_delta) { |
108 | ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, |
109 | "Modification is not setup and therefore cannot execute!" ); |
110 | if (!enabled) { |
111 | return; |
112 | } |
113 | |
114 | if (target_node_cache.is_null()) { |
115 | WARN_PRINT_ONCE("Target cache is out of date. Attempting to update..." ); |
116 | update_target_cache(); |
117 | return; |
118 | } |
119 | |
120 | if (bone2d_node_cache.is_null() && !bone2d_node.is_empty()) { |
121 | update_bone2d_cache(); |
122 | WARN_PRINT_ONCE("Bone2D node cache is out of date. Attempting to update..." ); |
123 | return; |
124 | } |
125 | |
126 | if (target_node_reference == nullptr) { |
127 | target_node_reference = Object::cast_to<Node2D>(ObjectDB::get_instance(target_node_cache)); |
128 | } |
129 | if (!target_node_reference || !target_node_reference->is_inside_tree()) { |
130 | ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!" ); |
131 | return; |
132 | } |
133 | if (bone_idx <= -1) { |
134 | ERR_PRINT_ONCE("Bone index is invalid. Cannot execute modification!" ); |
135 | return; |
136 | } |
137 | |
138 | Bone2D *operation_bone = stack->skeleton->get_bone(bone_idx); |
139 | if (operation_bone == nullptr) { |
140 | ERR_PRINT_ONCE("bone_idx for modification does not point to a valid bone! Cannot execute modification" ); |
141 | return; |
142 | } |
143 | |
144 | Transform2D operation_transform = operation_bone->get_global_transform(); |
145 | Transform2D target_trans = target_node_reference->get_global_transform(); |
146 | |
147 | // Look at the target! |
148 | operation_transform = operation_transform.looking_at(target_trans.get_origin()); |
149 | // Apply whatever scale it had prior to looking_at |
150 | operation_transform.set_scale(operation_bone->get_global_scale()); |
151 | |
152 | // Account for the direction the bone faces in: |
153 | operation_transform.set_rotation(operation_transform.get_rotation() - operation_bone->get_bone_angle()); |
154 | |
155 | // Apply additional rotation |
156 | operation_transform.set_rotation(operation_transform.get_rotation() + additional_rotation); |
157 | |
158 | // Apply constraints in globalspace: |
159 | if (enable_constraint && !constraint_in_localspace) { |
160 | operation_transform.set_rotation(clamp_angle(operation_transform.get_rotation(), constraint_angle_min, constraint_angle_max, constraint_angle_invert)); |
161 | } |
162 | |
163 | // Convert from a global transform to a local transform via the Bone2D node |
164 | operation_bone->set_global_transform(operation_transform); |
165 | operation_transform = operation_bone->get_transform(); |
166 | |
167 | // Apply constraints in localspace: |
168 | if (enable_constraint && constraint_in_localspace) { |
169 | operation_transform.set_rotation(clamp_angle(operation_transform.get_rotation(), constraint_angle_min, constraint_angle_max, constraint_angle_invert)); |
170 | } |
171 | |
172 | // Set the local pose override, and to make sure child bones are also updated, set the transform of the bone. |
173 | stack->skeleton->set_bone_local_pose_override(bone_idx, operation_transform, stack->strength, true); |
174 | operation_bone->set_transform(operation_transform); |
175 | } |
176 | |
177 | void SkeletonModification2DLookAt::_setup_modification(SkeletonModificationStack2D *p_stack) { |
178 | stack = p_stack; |
179 | |
180 | if (stack != nullptr) { |
181 | is_setup = true; |
182 | update_target_cache(); |
183 | update_bone2d_cache(); |
184 | } |
185 | } |
186 | |
187 | void SkeletonModification2DLookAt::_draw_editor_gizmo() { |
188 | if (!enabled || !is_setup) { |
189 | return; |
190 | } |
191 | |
192 | Bone2D *operation_bone = stack->skeleton->get_bone(bone_idx); |
193 | editor_draw_angle_constraints(operation_bone, constraint_angle_min, constraint_angle_max, |
194 | enable_constraint, constraint_in_localspace, constraint_angle_invert); |
195 | } |
196 | |
197 | void SkeletonModification2DLookAt::update_bone2d_cache() { |
198 | if (!is_setup || !stack) { |
199 | ERR_PRINT_ONCE("Cannot update Bone2D cache: modification is not properly setup!" ); |
200 | return; |
201 | } |
202 | |
203 | bone2d_node_cache = ObjectID(); |
204 | if (stack->skeleton) { |
205 | if (stack->skeleton->is_inside_tree()) { |
206 | if (stack->skeleton->has_node(bone2d_node)) { |
207 | Node *node = stack->skeleton->get_node(bone2d_node); |
208 | ERR_FAIL_COND_MSG(!node || stack->skeleton == node, |
209 | "Cannot update Bone2D cache: node is this modification's skeleton or cannot be found!" ); |
210 | ERR_FAIL_COND_MSG(!node->is_inside_tree(), |
211 | "Cannot update Bone2D cache: node is not in the scene tree!" ); |
212 | bone2d_node_cache = node->get_instance_id(); |
213 | |
214 | Bone2D *bone = Object::cast_to<Bone2D>(node); |
215 | if (bone) { |
216 | bone_idx = bone->get_index_in_skeleton(); |
217 | } else { |
218 | ERR_FAIL_MSG("Error Bone2D cache: Nodepath to Bone2D is not a Bone2D node!" ); |
219 | } |
220 | |
221 | // Set this to null so we update it |
222 | target_node_reference = nullptr; |
223 | } |
224 | } |
225 | } |
226 | } |
227 | |
228 | void SkeletonModification2DLookAt::set_bone2d_node(const NodePath &p_target_node) { |
229 | bone2d_node = p_target_node; |
230 | update_bone2d_cache(); |
231 | } |
232 | |
233 | NodePath SkeletonModification2DLookAt::get_bone2d_node() const { |
234 | return bone2d_node; |
235 | } |
236 | |
237 | int SkeletonModification2DLookAt::get_bone_index() const { |
238 | return bone_idx; |
239 | } |
240 | |
241 | void SkeletonModification2DLookAt::set_bone_index(int p_bone_idx) { |
242 | ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!" ); |
243 | |
244 | if (is_setup && stack) { |
245 | if (stack->skeleton) { |
246 | ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!" ); |
247 | bone_idx = p_bone_idx; |
248 | bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id(); |
249 | bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx)); |
250 | } else { |
251 | WARN_PRINT("Cannot verify the bone index for this modification..." ); |
252 | bone_idx = p_bone_idx; |
253 | } |
254 | } else { |
255 | WARN_PRINT("Cannot verify the bone index for this modification..." ); |
256 | bone_idx = p_bone_idx; |
257 | } |
258 | |
259 | notify_property_list_changed(); |
260 | } |
261 | |
262 | void SkeletonModification2DLookAt::update_target_cache() { |
263 | if (!is_setup || !stack) { |
264 | ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!" ); |
265 | return; |
266 | } |
267 | |
268 | target_node_cache = ObjectID(); |
269 | if (stack->skeleton) { |
270 | if (stack->skeleton->is_inside_tree()) { |
271 | if (stack->skeleton->has_node(target_node)) { |
272 | Node *node = stack->skeleton->get_node(target_node); |
273 | ERR_FAIL_COND_MSG(!node || stack->skeleton == node, |
274 | "Cannot update target cache: node is this modification's skeleton or cannot be found!" ); |
275 | ERR_FAIL_COND_MSG(!node->is_inside_tree(), |
276 | "Cannot update target cache: node is not in the scene tree!" ); |
277 | target_node_cache = node->get_instance_id(); |
278 | } |
279 | } |
280 | } |
281 | } |
282 | |
283 | void SkeletonModification2DLookAt::set_target_node(const NodePath &p_target_node) { |
284 | target_node = p_target_node; |
285 | update_target_cache(); |
286 | } |
287 | |
288 | NodePath SkeletonModification2DLookAt::get_target_node() const { |
289 | return target_node; |
290 | } |
291 | |
292 | float SkeletonModification2DLookAt::get_additional_rotation() const { |
293 | return additional_rotation; |
294 | } |
295 | |
296 | void SkeletonModification2DLookAt::set_additional_rotation(float p_rotation) { |
297 | additional_rotation = p_rotation; |
298 | } |
299 | |
300 | void SkeletonModification2DLookAt::set_enable_constraint(bool p_constraint) { |
301 | enable_constraint = p_constraint; |
302 | notify_property_list_changed(); |
303 | #ifdef TOOLS_ENABLED |
304 | if (stack && is_setup) { |
305 | stack->set_editor_gizmos_dirty(true); |
306 | } |
307 | #endif // TOOLS_ENABLED |
308 | } |
309 | |
310 | bool SkeletonModification2DLookAt::get_enable_constraint() const { |
311 | return enable_constraint; |
312 | } |
313 | |
314 | void SkeletonModification2DLookAt::set_constraint_angle_min(float p_angle_min) { |
315 | constraint_angle_min = p_angle_min; |
316 | #ifdef TOOLS_ENABLED |
317 | if (stack && is_setup) { |
318 | stack->set_editor_gizmos_dirty(true); |
319 | } |
320 | #endif // TOOLS_ENABLED |
321 | } |
322 | |
323 | float SkeletonModification2DLookAt::get_constraint_angle_min() const { |
324 | return constraint_angle_min; |
325 | } |
326 | |
327 | void SkeletonModification2DLookAt::set_constraint_angle_max(float p_angle_max) { |
328 | constraint_angle_max = p_angle_max; |
329 | #ifdef TOOLS_ENABLED |
330 | if (stack && is_setup) { |
331 | stack->set_editor_gizmos_dirty(true); |
332 | } |
333 | #endif // TOOLS_ENABLED |
334 | } |
335 | |
336 | float SkeletonModification2DLookAt::get_constraint_angle_max() const { |
337 | return constraint_angle_max; |
338 | } |
339 | |
340 | void SkeletonModification2DLookAt::set_constraint_angle_invert(bool p_invert) { |
341 | constraint_angle_invert = p_invert; |
342 | #ifdef TOOLS_ENABLED |
343 | if (stack && is_setup) { |
344 | stack->set_editor_gizmos_dirty(true); |
345 | } |
346 | #endif // TOOLS_ENABLED |
347 | } |
348 | |
349 | bool SkeletonModification2DLookAt::get_constraint_angle_invert() const { |
350 | return constraint_angle_invert; |
351 | } |
352 | |
353 | void SkeletonModification2DLookAt::set_constraint_in_localspace(bool p_constraint_in_localspace) { |
354 | constraint_in_localspace = p_constraint_in_localspace; |
355 | #ifdef TOOLS_ENABLED |
356 | if (stack && is_setup) { |
357 | stack->set_editor_gizmos_dirty(true); |
358 | } |
359 | #endif // TOOLS_ENABLED |
360 | } |
361 | |
362 | bool SkeletonModification2DLookAt::get_constraint_in_localspace() const { |
363 | return constraint_in_localspace; |
364 | } |
365 | |
366 | void SkeletonModification2DLookAt::_bind_methods() { |
367 | ClassDB::bind_method(D_METHOD("set_bone2d_node" , "bone2d_nodepath" ), &SkeletonModification2DLookAt::set_bone2d_node); |
368 | ClassDB::bind_method(D_METHOD("get_bone2d_node" ), &SkeletonModification2DLookAt::get_bone2d_node); |
369 | ClassDB::bind_method(D_METHOD("set_bone_index" , "bone_idx" ), &SkeletonModification2DLookAt::set_bone_index); |
370 | ClassDB::bind_method(D_METHOD("get_bone_index" ), &SkeletonModification2DLookAt::get_bone_index); |
371 | |
372 | ClassDB::bind_method(D_METHOD("set_target_node" , "target_nodepath" ), &SkeletonModification2DLookAt::set_target_node); |
373 | ClassDB::bind_method(D_METHOD("get_target_node" ), &SkeletonModification2DLookAt::get_target_node); |
374 | |
375 | ClassDB::bind_method(D_METHOD("set_additional_rotation" , "rotation" ), &SkeletonModification2DLookAt::set_additional_rotation); |
376 | ClassDB::bind_method(D_METHOD("get_additional_rotation" ), &SkeletonModification2DLookAt::get_additional_rotation); |
377 | |
378 | ClassDB::bind_method(D_METHOD("set_enable_constraint" , "enable_constraint" ), &SkeletonModification2DLookAt::set_enable_constraint); |
379 | ClassDB::bind_method(D_METHOD("get_enable_constraint" ), &SkeletonModification2DLookAt::get_enable_constraint); |
380 | ClassDB::bind_method(D_METHOD("set_constraint_angle_min" , "angle_min" ), &SkeletonModification2DLookAt::set_constraint_angle_min); |
381 | ClassDB::bind_method(D_METHOD("get_constraint_angle_min" ), &SkeletonModification2DLookAt::get_constraint_angle_min); |
382 | ClassDB::bind_method(D_METHOD("set_constraint_angle_max" , "angle_max" ), &SkeletonModification2DLookAt::set_constraint_angle_max); |
383 | ClassDB::bind_method(D_METHOD("get_constraint_angle_max" ), &SkeletonModification2DLookAt::get_constraint_angle_max); |
384 | ClassDB::bind_method(D_METHOD("set_constraint_angle_invert" , "invert" ), &SkeletonModification2DLookAt::set_constraint_angle_invert); |
385 | ClassDB::bind_method(D_METHOD("get_constraint_angle_invert" ), &SkeletonModification2DLookAt::get_constraint_angle_invert); |
386 | |
387 | ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_index" ), "set_bone_index" , "get_bone_index" ); |
388 | ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "bone2d_node" , PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D" ), "set_bone2d_node" , "get_bone2d_node" ); |
389 | ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath" , PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D" ), "set_target_node" , "get_target_node" ); |
390 | } |
391 | |
392 | SkeletonModification2DLookAt::SkeletonModification2DLookAt() { |
393 | stack = nullptr; |
394 | is_setup = false; |
395 | bone_idx = -1; |
396 | additional_rotation = 0; |
397 | enable_constraint = false; |
398 | constraint_angle_min = 0; |
399 | constraint_angle_max = Math_PI * 2; |
400 | constraint_angle_invert = false; |
401 | enabled = true; |
402 | |
403 | editor_draw_gizmo = true; |
404 | } |
405 | |
406 | SkeletonModification2DLookAt::~SkeletonModification2DLookAt() { |
407 | } |
408 | |