1 | /**************************************************************************/ |
2 | /* line_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 "line_2d.h" |
32 | |
33 | #include "core/math/geometry_2d.h" |
34 | #include "line_builder.h" |
35 | |
36 | Line2D::Line2D() { |
37 | } |
38 | |
39 | #ifdef TOOLS_ENABLED |
40 | Rect2 Line2D::_edit_get_rect() const { |
41 | if (_points.size() == 0) { |
42 | return Rect2(0, 0, 0, 0); |
43 | } |
44 | Vector2 d = Vector2(_width, _width); |
45 | Rect2 bounding_rect = Rect2(_points[0] - d, 2 * d); |
46 | for (int i = 1; i < _points.size(); i++) { |
47 | bounding_rect.expand_to(_points[i] - d); |
48 | bounding_rect.expand_to(_points[i] + d); |
49 | } |
50 | return bounding_rect; |
51 | } |
52 | |
53 | bool Line2D::_edit_use_rect() const { |
54 | return true; |
55 | } |
56 | |
57 | bool Line2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { |
58 | const real_t d = _width / 2 + p_tolerance; |
59 | const Vector2 *points = _points.ptr(); |
60 | for (int i = 0; i < _points.size() - 1; i++) { |
61 | Vector2 p = Geometry2D::get_closest_point_to_segment(p_point, &points[i]); |
62 | if (p_point.distance_to(p) <= d) { |
63 | return true; |
64 | } |
65 | } |
66 | if (_closed && _points.size() > 2) { |
67 | const Vector2 closing_segment[2] = { points[0], points[_points.size() - 1] }; |
68 | Vector2 p = Geometry2D::get_closest_point_to_segment(p_point, closing_segment); |
69 | if (p_point.distance_to(p) <= d) { |
70 | return true; |
71 | } |
72 | } |
73 | |
74 | return false; |
75 | } |
76 | #endif |
77 | |
78 | void Line2D::set_points(const Vector<Vector2> &p_points) { |
79 | _points = p_points; |
80 | queue_redraw(); |
81 | } |
82 | |
83 | void Line2D::set_closed(bool p_closed) { |
84 | _closed = p_closed; |
85 | queue_redraw(); |
86 | } |
87 | |
88 | bool Line2D::is_closed() const { |
89 | return _closed; |
90 | } |
91 | |
92 | void Line2D::set_width(float p_width) { |
93 | if (p_width < 0.0) { |
94 | p_width = 0.0; |
95 | } |
96 | _width = p_width; |
97 | queue_redraw(); |
98 | } |
99 | |
100 | float Line2D::get_width() const { |
101 | return _width; |
102 | } |
103 | |
104 | void Line2D::set_curve(const Ref<Curve> &p_curve) { |
105 | if (_curve.is_valid()) { |
106 | _curve->disconnect_changed(callable_mp(this, &Line2D::_curve_changed)); |
107 | } |
108 | |
109 | _curve = p_curve; |
110 | |
111 | if (_curve.is_valid()) { |
112 | _curve->connect_changed(callable_mp(this, &Line2D::_curve_changed)); |
113 | } |
114 | |
115 | queue_redraw(); |
116 | } |
117 | |
118 | Ref<Curve> Line2D::get_curve() const { |
119 | return _curve; |
120 | } |
121 | |
122 | Vector<Vector2> Line2D::get_points() const { |
123 | return _points; |
124 | } |
125 | |
126 | void Line2D::set_point_position(int i, Vector2 p_pos) { |
127 | ERR_FAIL_INDEX(i, _points.size()); |
128 | _points.set(i, p_pos); |
129 | queue_redraw(); |
130 | } |
131 | |
132 | Vector2 Line2D::get_point_position(int i) const { |
133 | ERR_FAIL_INDEX_V(i, _points.size(), Vector2()); |
134 | return _points.get(i); |
135 | } |
136 | |
137 | int Line2D::get_point_count() const { |
138 | return _points.size(); |
139 | } |
140 | |
141 | void Line2D::clear_points() { |
142 | int count = _points.size(); |
143 | if (count > 0) { |
144 | _points.clear(); |
145 | queue_redraw(); |
146 | } |
147 | } |
148 | |
149 | void Line2D::add_point(Vector2 p_pos, int p_atpos) { |
150 | if (p_atpos < 0 || _points.size() < p_atpos) { |
151 | _points.push_back(p_pos); |
152 | } else { |
153 | _points.insert(p_atpos, p_pos); |
154 | } |
155 | queue_redraw(); |
156 | } |
157 | |
158 | void Line2D::remove_point(int i) { |
159 | _points.remove_at(i); |
160 | queue_redraw(); |
161 | } |
162 | |
163 | void Line2D::set_default_color(Color p_color) { |
164 | _default_color = p_color; |
165 | queue_redraw(); |
166 | } |
167 | |
168 | Color Line2D::get_default_color() const { |
169 | return _default_color; |
170 | } |
171 | |
172 | void Line2D::set_gradient(const Ref<Gradient> &p_gradient) { |
173 | if (_gradient.is_valid()) { |
174 | _gradient->disconnect_changed(callable_mp(this, &Line2D::_gradient_changed)); |
175 | } |
176 | |
177 | _gradient = p_gradient; |
178 | |
179 | if (_gradient.is_valid()) { |
180 | _gradient->connect_changed(callable_mp(this, &Line2D::_gradient_changed)); |
181 | } |
182 | |
183 | queue_redraw(); |
184 | } |
185 | |
186 | Ref<Gradient> Line2D::get_gradient() const { |
187 | return _gradient; |
188 | } |
189 | |
190 | void Line2D::set_texture(const Ref<Texture2D> &p_texture) { |
191 | _texture = p_texture; |
192 | queue_redraw(); |
193 | } |
194 | |
195 | Ref<Texture2D> Line2D::get_texture() const { |
196 | return _texture; |
197 | } |
198 | |
199 | void Line2D::set_texture_mode(const LineTextureMode p_mode) { |
200 | _texture_mode = p_mode; |
201 | queue_redraw(); |
202 | } |
203 | |
204 | Line2D::LineTextureMode Line2D::get_texture_mode() const { |
205 | return _texture_mode; |
206 | } |
207 | |
208 | void Line2D::set_joint_mode(LineJointMode p_mode) { |
209 | _joint_mode = p_mode; |
210 | queue_redraw(); |
211 | } |
212 | |
213 | Line2D::LineJointMode Line2D::get_joint_mode() const { |
214 | return _joint_mode; |
215 | } |
216 | |
217 | void Line2D::set_begin_cap_mode(LineCapMode p_mode) { |
218 | _begin_cap_mode = p_mode; |
219 | queue_redraw(); |
220 | } |
221 | |
222 | Line2D::LineCapMode Line2D::get_begin_cap_mode() const { |
223 | return _begin_cap_mode; |
224 | } |
225 | |
226 | void Line2D::set_end_cap_mode(LineCapMode p_mode) { |
227 | _end_cap_mode = p_mode; |
228 | queue_redraw(); |
229 | } |
230 | |
231 | Line2D::LineCapMode Line2D::get_end_cap_mode() const { |
232 | return _end_cap_mode; |
233 | } |
234 | |
235 | void Line2D::_notification(int p_what) { |
236 | switch (p_what) { |
237 | case NOTIFICATION_DRAW: { |
238 | _draw(); |
239 | } break; |
240 | } |
241 | } |
242 | |
243 | void Line2D::set_sharp_limit(float p_limit) { |
244 | if (p_limit < 0.f) { |
245 | p_limit = 0.f; |
246 | } |
247 | _sharp_limit = p_limit; |
248 | queue_redraw(); |
249 | } |
250 | |
251 | float Line2D::get_sharp_limit() const { |
252 | return _sharp_limit; |
253 | } |
254 | |
255 | void Line2D::set_round_precision(int p_precision) { |
256 | _round_precision = MAX(1, p_precision); |
257 | queue_redraw(); |
258 | } |
259 | |
260 | int Line2D::get_round_precision() const { |
261 | return _round_precision; |
262 | } |
263 | |
264 | void Line2D::set_antialiased(bool p_antialiased) { |
265 | _antialiased = p_antialiased; |
266 | queue_redraw(); |
267 | } |
268 | |
269 | bool Line2D::get_antialiased() const { |
270 | return _antialiased; |
271 | } |
272 | |
273 | void Line2D::_draw() { |
274 | int len = _points.size(); |
275 | if (len <= 1 || _width == 0.f) { |
276 | return; |
277 | } |
278 | |
279 | // TODO Maybe have it as member rather than copying parameters and allocating memory? |
280 | LineBuilder lb; |
281 | lb.points = _points; |
282 | lb.closed = _closed; |
283 | lb.default_color = _default_color; |
284 | lb.gradient = *_gradient; |
285 | lb.texture_mode = _texture_mode; |
286 | lb.joint_mode = _joint_mode; |
287 | lb.begin_cap_mode = _begin_cap_mode; |
288 | lb.end_cap_mode = _end_cap_mode; |
289 | lb.round_precision = _round_precision; |
290 | lb.sharp_limit = _sharp_limit; |
291 | lb.width = _width; |
292 | lb.curve = *_curve; |
293 | |
294 | RID texture_rid; |
295 | if (_texture.is_valid()) { |
296 | texture_rid = _texture->get_rid(); |
297 | |
298 | lb.tile_aspect = _texture->get_size().aspect(); |
299 | } |
300 | |
301 | lb.build(); |
302 | |
303 | RS::get_singleton()->canvas_item_add_triangle_array( |
304 | get_canvas_item(), |
305 | lb.indices, |
306 | lb.vertices, |
307 | lb.colors, |
308 | lb.uvs, Vector<int>(), Vector<float>(), |
309 | texture_rid); |
310 | |
311 | // DEBUG: Draw wireframe |
312 | // if (lb.indices.size() % 3 == 0) { |
313 | // Color col(0, 0, 0); |
314 | // for (int i = 0; i < lb.indices.size(); i += 3) { |
315 | // Vector2 a = lb.vertices[lb.indices[i]]; |
316 | // Vector2 b = lb.vertices[lb.indices[i+1]]; |
317 | // Vector2 c = lb.vertices[lb.indices[i+2]]; |
318 | // draw_line(a, b, col); |
319 | // draw_line(b, c, col); |
320 | // draw_line(c, a, col); |
321 | // } |
322 | // for (int i = 0; i < lb.vertices.size(); ++i) { |
323 | // Vector2 p = lb.vertices[i]; |
324 | // draw_rect(Rect2(p.x - 1, p.y - 1, 2, 2), Color(0, 0, 0, 0.5)); |
325 | // } |
326 | // } |
327 | } |
328 | |
329 | void Line2D::_gradient_changed() { |
330 | queue_redraw(); |
331 | } |
332 | |
333 | void Line2D::_curve_changed() { |
334 | queue_redraw(); |
335 | } |
336 | |
337 | // static |
338 | void Line2D::_bind_methods() { |
339 | ClassDB::bind_method(D_METHOD("set_points" , "points" ), &Line2D::set_points); |
340 | ClassDB::bind_method(D_METHOD("get_points" ), &Line2D::get_points); |
341 | |
342 | ClassDB::bind_method(D_METHOD("set_point_position" , "index" , "position" ), &Line2D::set_point_position); |
343 | ClassDB::bind_method(D_METHOD("get_point_position" , "index" ), &Line2D::get_point_position); |
344 | |
345 | ClassDB::bind_method(D_METHOD("get_point_count" ), &Line2D::get_point_count); |
346 | |
347 | ClassDB::bind_method(D_METHOD("add_point" , "position" , "index" ), &Line2D::add_point, DEFVAL(-1)); |
348 | ClassDB::bind_method(D_METHOD("remove_point" , "index" ), &Line2D::remove_point); |
349 | |
350 | ClassDB::bind_method(D_METHOD("clear_points" ), &Line2D::clear_points); |
351 | |
352 | ClassDB::bind_method(D_METHOD("set_closed" , "closed" ), &Line2D::set_closed); |
353 | ClassDB::bind_method(D_METHOD("is_closed" ), &Line2D::is_closed); |
354 | |
355 | ClassDB::bind_method(D_METHOD("set_width" , "width" ), &Line2D::set_width); |
356 | ClassDB::bind_method(D_METHOD("get_width" ), &Line2D::get_width); |
357 | |
358 | ClassDB::bind_method(D_METHOD("set_curve" , "curve" ), &Line2D::set_curve); |
359 | ClassDB::bind_method(D_METHOD("get_curve" ), &Line2D::get_curve); |
360 | |
361 | ClassDB::bind_method(D_METHOD("set_default_color" , "color" ), &Line2D::set_default_color); |
362 | ClassDB::bind_method(D_METHOD("get_default_color" ), &Line2D::get_default_color); |
363 | |
364 | ClassDB::bind_method(D_METHOD("set_gradient" , "color" ), &Line2D::set_gradient); |
365 | ClassDB::bind_method(D_METHOD("get_gradient" ), &Line2D::get_gradient); |
366 | |
367 | ClassDB::bind_method(D_METHOD("set_texture" , "texture" ), &Line2D::set_texture); |
368 | ClassDB::bind_method(D_METHOD("get_texture" ), &Line2D::get_texture); |
369 | |
370 | ClassDB::bind_method(D_METHOD("set_texture_mode" , "mode" ), &Line2D::set_texture_mode); |
371 | ClassDB::bind_method(D_METHOD("get_texture_mode" ), &Line2D::get_texture_mode); |
372 | |
373 | ClassDB::bind_method(D_METHOD("set_joint_mode" , "mode" ), &Line2D::set_joint_mode); |
374 | ClassDB::bind_method(D_METHOD("get_joint_mode" ), &Line2D::get_joint_mode); |
375 | |
376 | ClassDB::bind_method(D_METHOD("set_begin_cap_mode" , "mode" ), &Line2D::set_begin_cap_mode); |
377 | ClassDB::bind_method(D_METHOD("get_begin_cap_mode" ), &Line2D::get_begin_cap_mode); |
378 | |
379 | ClassDB::bind_method(D_METHOD("set_end_cap_mode" , "mode" ), &Line2D::set_end_cap_mode); |
380 | ClassDB::bind_method(D_METHOD("get_end_cap_mode" ), &Line2D::get_end_cap_mode); |
381 | |
382 | ClassDB::bind_method(D_METHOD("set_sharp_limit" , "limit" ), &Line2D::set_sharp_limit); |
383 | ClassDB::bind_method(D_METHOD("get_sharp_limit" ), &Line2D::get_sharp_limit); |
384 | |
385 | ClassDB::bind_method(D_METHOD("set_round_precision" , "precision" ), &Line2D::set_round_precision); |
386 | ClassDB::bind_method(D_METHOD("get_round_precision" ), &Line2D::get_round_precision); |
387 | |
388 | ClassDB::bind_method(D_METHOD("set_antialiased" , "antialiased" ), &Line2D::set_antialiased); |
389 | ClassDB::bind_method(D_METHOD("get_antialiased" ), &Line2D::get_antialiased); |
390 | |
391 | ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "points" ), "set_points" , "get_points" ); |
392 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "closed" ), "set_closed" , "is_closed" ); |
393 | ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "width" , PROPERTY_HINT_NONE, "suffix:px" ), "set_width" , "get_width" ); |
394 | ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "width_curve" , PROPERTY_HINT_RESOURCE_TYPE, "Curve" ), "set_curve" , "get_curve" ); |
395 | ADD_PROPERTY(PropertyInfo(Variant::COLOR, "default_color" ), "set_default_color" , "get_default_color" ); |
396 | ADD_GROUP("Fill" , "" ); |
397 | ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gradient" , PROPERTY_HINT_RESOURCE_TYPE, "Gradient" ), "set_gradient" , "get_gradient" ); |
398 | ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture" , PROPERTY_HINT_RESOURCE_TYPE, "Texture2D" ), "set_texture" , "get_texture" ); |
399 | ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_mode" , PROPERTY_HINT_ENUM, "None,Tile,Stretch" ), "set_texture_mode" , "get_texture_mode" ); |
400 | ADD_GROUP("Capping" , "" ); |
401 | ADD_PROPERTY(PropertyInfo(Variant::INT, "joint_mode" , PROPERTY_HINT_ENUM, "Sharp,Bevel,Round" ), "set_joint_mode" , "get_joint_mode" ); |
402 | ADD_PROPERTY(PropertyInfo(Variant::INT, "begin_cap_mode" , PROPERTY_HINT_ENUM, "None,Box,Round" ), "set_begin_cap_mode" , "get_begin_cap_mode" ); |
403 | ADD_PROPERTY(PropertyInfo(Variant::INT, "end_cap_mode" , PROPERTY_HINT_ENUM, "None,Box,Round" ), "set_end_cap_mode" , "get_end_cap_mode" ); |
404 | ADD_GROUP("Border" , "" ); |
405 | ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sharp_limit" ), "set_sharp_limit" , "get_sharp_limit" ); |
406 | ADD_PROPERTY(PropertyInfo(Variant::INT, "round_precision" , PROPERTY_HINT_RANGE, "1,32,1" ), "set_round_precision" , "get_round_precision" ); |
407 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "antialiased" ), "set_antialiased" , "get_antialiased" ); |
408 | |
409 | BIND_ENUM_CONSTANT(LINE_JOINT_SHARP); |
410 | BIND_ENUM_CONSTANT(LINE_JOINT_BEVEL); |
411 | BIND_ENUM_CONSTANT(LINE_JOINT_ROUND); |
412 | |
413 | BIND_ENUM_CONSTANT(LINE_CAP_NONE); |
414 | BIND_ENUM_CONSTANT(LINE_CAP_BOX); |
415 | BIND_ENUM_CONSTANT(LINE_CAP_ROUND); |
416 | |
417 | BIND_ENUM_CONSTANT(LINE_TEXTURE_NONE); |
418 | BIND_ENUM_CONSTANT(LINE_TEXTURE_TILE); |
419 | BIND_ENUM_CONSTANT(LINE_TEXTURE_STRETCH); |
420 | } |
421 | |