1/**************************************************************************/
2/* collision_polygon_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 "collision_polygon_2d.h"
32
33#include "collision_object_2d.h"
34#include "core/math/geometry_2d.h"
35#include "scene/2d/area_2d.h"
36#include "scene/resources/concave_polygon_shape_2d.h"
37#include "scene/resources/convex_polygon_shape_2d.h"
38
39#include "thirdparty/misc/polypartition.h"
40
41void CollisionPolygon2D::_build_polygon() {
42 collision_object->shape_owner_clear_shapes(owner_id);
43
44 bool solids = build_mode == BUILD_SOLIDS;
45
46 if (solids) {
47 if (polygon.size() < 3) {
48 return;
49 }
50
51 //here comes the sun, lalalala
52 //decompose concave into multiple convex polygons and add them
53 Vector<Vector<Vector2>> decomp = _decompose_in_convex();
54 for (int i = 0; i < decomp.size(); i++) {
55 Ref<ConvexPolygonShape2D> convex = memnew(ConvexPolygonShape2D);
56 convex->set_points(decomp[i]);
57 collision_object->shape_owner_add_shape(owner_id, convex);
58 }
59
60 } else {
61 if (polygon.size() < 2) {
62 return;
63 }
64
65 Ref<ConcavePolygonShape2D> concave = memnew(ConcavePolygonShape2D);
66
67 Vector<Vector2> segments;
68 segments.resize(polygon.size() * 2);
69 Vector2 *w = segments.ptrw();
70
71 for (int i = 0; i < polygon.size(); i++) {
72 w[(i << 1) + 0] = polygon[i];
73 w[(i << 1) + 1] = polygon[(i + 1) % polygon.size()];
74 }
75
76 concave->set_segments(segments);
77
78 collision_object->shape_owner_add_shape(owner_id, concave);
79 }
80}
81
82Vector<Vector<Vector2>> CollisionPolygon2D::_decompose_in_convex() {
83 Vector<Vector<Vector2>> decomp = Geometry2D::decompose_polygon_in_convex(polygon);
84 return decomp;
85}
86
87void CollisionPolygon2D::_update_in_shape_owner(bool p_xform_only) {
88 collision_object->shape_owner_set_transform(owner_id, get_transform());
89 if (p_xform_only) {
90 return;
91 }
92 collision_object->shape_owner_set_disabled(owner_id, disabled);
93 collision_object->shape_owner_set_one_way_collision(owner_id, one_way_collision);
94 collision_object->shape_owner_set_one_way_collision_margin(owner_id, one_way_collision_margin);
95}
96
97void CollisionPolygon2D::_notification(int p_what) {
98 switch (p_what) {
99 case NOTIFICATION_PARENTED: {
100 collision_object = Object::cast_to<CollisionObject2D>(get_parent());
101 if (collision_object) {
102 owner_id = collision_object->create_shape_owner(this);
103 _build_polygon();
104 _update_in_shape_owner();
105 }
106 } break;
107
108 case NOTIFICATION_ENTER_TREE: {
109 if (collision_object) {
110 _update_in_shape_owner();
111 }
112 } break;
113
114 case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: {
115 if (collision_object) {
116 _update_in_shape_owner(true);
117 }
118 } break;
119
120 case NOTIFICATION_UNPARENTED: {
121 if (collision_object) {
122 collision_object->remove_shape_owner(owner_id);
123 }
124 owner_id = 0;
125 collision_object = nullptr;
126 } break;
127
128 case NOTIFICATION_DRAW: {
129 ERR_FAIL_COND(!is_inside_tree());
130 if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) {
131 break;
132 }
133
134 if (polygon.size() > 2) {
135#define DEBUG_DECOMPOSE
136#if defined(TOOLS_ENABLED) && defined(DEBUG_DECOMPOSE)
137 Vector<Vector<Vector2>> decomp = _decompose_in_convex();
138
139 Color c(0.4, 0.9, 0.1);
140 for (int i = 0; i < decomp.size(); i++) {
141 c.set_hsv(Math::fmod(c.get_h() + 0.738, 1), c.get_s(), c.get_v(), 0.5);
142 draw_colored_polygon(decomp[i], c);
143 }
144#else
145 draw_colored_polygon(polygon, get_tree()->get_debug_collisions_color());
146#endif
147
148 const Color stroke_color = Color(0.9, 0.2, 0.0);
149 draw_polyline(polygon, stroke_color);
150 // Draw the last segment.
151 draw_line(polygon[polygon.size() - 1], polygon[0], stroke_color);
152 }
153
154 if (one_way_collision) {
155 Color dcol = get_tree()->get_debug_collisions_color(); //0.9,0.2,0.2,0.4);
156 dcol.a = 1.0;
157 Vector2 line_to(0, 20);
158 draw_line(Vector2(), line_to, dcol, 3);
159 real_t tsize = 8;
160
161 Vector<Vector2> pts = {
162 line_to + Vector2(0, tsize),
163 line_to + Vector2(Math_SQRT12 * tsize, 0),
164 line_to + Vector2(-Math_SQRT12 * tsize, 0)
165 };
166
167 Vector<Color> cols{ dcol, dcol, dcol };
168
169 draw_primitive(pts, cols, Vector<Vector2>()); //small arrow
170 }
171 } break;
172 }
173}
174
175void CollisionPolygon2D::set_polygon(const Vector<Point2> &p_polygon) {
176 polygon = p_polygon;
177
178 {
179 for (int i = 0; i < polygon.size(); i++) {
180 if (i == 0) {
181 aabb = Rect2(polygon[i], Size2());
182 } else {
183 aabb.expand_to(polygon[i]);
184 }
185 }
186 if (aabb == Rect2()) {
187 aabb = Rect2(-10, -10, 20, 20);
188 } else {
189 aabb.position -= aabb.size * 0.3;
190 aabb.size += aabb.size * 0.6;
191 }
192 }
193
194 if (collision_object) {
195 _build_polygon();
196 _update_in_shape_owner();
197 }
198 queue_redraw();
199 update_configuration_warnings();
200}
201
202Vector<Point2> CollisionPolygon2D::get_polygon() const {
203 return polygon;
204}
205
206void CollisionPolygon2D::set_build_mode(BuildMode p_mode) {
207 ERR_FAIL_INDEX((int)p_mode, 2);
208 build_mode = p_mode;
209 if (collision_object) {
210 _build_polygon();
211 _update_in_shape_owner();
212 }
213 queue_redraw();
214 update_configuration_warnings();
215}
216
217CollisionPolygon2D::BuildMode CollisionPolygon2D::get_build_mode() const {
218 return build_mode;
219}
220
221#ifdef TOOLS_ENABLED
222Rect2 CollisionPolygon2D::_edit_get_rect() const {
223 return aabb;
224}
225
226bool CollisionPolygon2D::_edit_use_rect() const {
227 return true;
228}
229
230bool CollisionPolygon2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
231 return Geometry2D::is_point_in_polygon(p_point, Variant(polygon));
232}
233#endif
234
235PackedStringArray CollisionPolygon2D::get_configuration_warnings() const {
236 PackedStringArray warnings = Node::get_configuration_warnings();
237
238 if (!Object::cast_to<CollisionObject2D>(get_parent())) {
239 warnings.push_back(RTR("CollisionPolygon2D only serves to provide a collision shape to a CollisionObject2D derived node. Please only use it as a child of Area2D, StaticBody2D, RigidBody2D, CharacterBody2D, etc. to give them a shape."));
240 }
241
242 int polygon_count = polygon.size();
243 if (polygon_count == 0) {
244 warnings.push_back(RTR("An empty CollisionPolygon2D has no effect on collision."));
245 } else {
246 bool solids = build_mode == BUILD_SOLIDS;
247 if (solids) {
248 if (polygon_count < 3) {
249 warnings.push_back(RTR("Invalid polygon. At least 3 points are needed in 'Solids' build mode."));
250 }
251 } else if (polygon_count < 2) {
252 warnings.push_back(RTR("Invalid polygon. At least 2 points are needed in 'Segments' build mode."));
253 }
254 }
255 if (one_way_collision && Object::cast_to<Area2D>(get_parent())) {
256 warnings.push_back(RTR("The One Way Collision property will be ignored when the collision object is an Area2D."));
257 }
258
259 return warnings;
260}
261
262void CollisionPolygon2D::set_disabled(bool p_disabled) {
263 disabled = p_disabled;
264 queue_redraw();
265 if (collision_object) {
266 collision_object->shape_owner_set_disabled(owner_id, p_disabled);
267 }
268}
269
270bool CollisionPolygon2D::is_disabled() const {
271 return disabled;
272}
273
274void CollisionPolygon2D::set_one_way_collision(bool p_enable) {
275 one_way_collision = p_enable;
276 queue_redraw();
277 if (collision_object) {
278 collision_object->shape_owner_set_one_way_collision(owner_id, p_enable);
279 }
280 update_configuration_warnings();
281}
282
283bool CollisionPolygon2D::is_one_way_collision_enabled() const {
284 return one_way_collision;
285}
286
287void CollisionPolygon2D::set_one_way_collision_margin(real_t p_margin) {
288 one_way_collision_margin = p_margin;
289 if (collision_object) {
290 collision_object->shape_owner_set_one_way_collision_margin(owner_id, one_way_collision_margin);
291 }
292}
293
294real_t CollisionPolygon2D::get_one_way_collision_margin() const {
295 return one_way_collision_margin;
296}
297
298void CollisionPolygon2D::_bind_methods() {
299 ClassDB::bind_method(D_METHOD("set_polygon", "polygon"), &CollisionPolygon2D::set_polygon);
300 ClassDB::bind_method(D_METHOD("get_polygon"), &CollisionPolygon2D::get_polygon);
301
302 ClassDB::bind_method(D_METHOD("set_build_mode", "build_mode"), &CollisionPolygon2D::set_build_mode);
303 ClassDB::bind_method(D_METHOD("get_build_mode"), &CollisionPolygon2D::get_build_mode);
304 ClassDB::bind_method(D_METHOD("set_disabled", "disabled"), &CollisionPolygon2D::set_disabled);
305 ClassDB::bind_method(D_METHOD("is_disabled"), &CollisionPolygon2D::is_disabled);
306 ClassDB::bind_method(D_METHOD("set_one_way_collision", "enabled"), &CollisionPolygon2D::set_one_way_collision);
307 ClassDB::bind_method(D_METHOD("is_one_way_collision_enabled"), &CollisionPolygon2D::is_one_way_collision_enabled);
308 ClassDB::bind_method(D_METHOD("set_one_way_collision_margin", "margin"), &CollisionPolygon2D::set_one_way_collision_margin);
309 ClassDB::bind_method(D_METHOD("get_one_way_collision_margin"), &CollisionPolygon2D::get_one_way_collision_margin);
310
311 ADD_PROPERTY(PropertyInfo(Variant::INT, "build_mode", PROPERTY_HINT_ENUM, "Solids,Segments"), "set_build_mode", "get_build_mode");
312 ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "polygon"), "set_polygon", "get_polygon");
313 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled");
314 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_way_collision"), "set_one_way_collision", "is_one_way_collision_enabled");
315 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "one_way_collision_margin", PROPERTY_HINT_RANGE, "0,128,0.1,suffix:px"), "set_one_way_collision_margin", "get_one_way_collision_margin");
316
317 BIND_ENUM_CONSTANT(BUILD_SOLIDS);
318 BIND_ENUM_CONSTANT(BUILD_SEGMENTS);
319}
320
321CollisionPolygon2D::CollisionPolygon2D() {
322 set_notify_local_transform(true);
323 set_hide_clip_children(true);
324}
325