From e2fba10b952f694f604e20b9e5f220023a5f8fd2 Mon Sep 17 00:00:00 2001 From: Marc Gilleron Date: Fri, 9 Dec 2016 02:52:40 +0100 Subject: [PATCH] Added Line2D node that draws a polygon-based line It supports unlimited width, color gradient, texture and some geometry options for joints and caps. Also transparency without artifacts (provided that line joints aren't too sharp). --- scene/2d/line_2d.cpp | 307 ++++++++++ scene/2d/line_2d.h | 80 +++ scene/2d/line_builder.cpp | 563 ++++++++++++++++++ scene/2d/line_builder.h | 76 +++ scene/register_scene_types.cpp | 3 + scene/resources/color_ramp.cpp | 8 + tools/editor/editor_node.cpp | 2 + tools/editor/icons/2x/icon_line_2d.png | Bin 0 -> 795 bytes tools/editor/icons/icon_line_2d.png | Bin 0 -> 474 bytes tools/editor/icons/source/icon_line_2d.svg | 82 +++ .../editor/plugins/line_2d_editor_plugin.cpp | 283 +++++++++ tools/editor/plugins/line_2d_editor_plugin.h | 85 +++ 12 files changed, 1489 insertions(+) create mode 100644 scene/2d/line_2d.cpp create mode 100644 scene/2d/line_2d.h create mode 100644 scene/2d/line_builder.cpp create mode 100644 scene/2d/line_builder.h create mode 100644 tools/editor/icons/2x/icon_line_2d.png create mode 100644 tools/editor/icons/icon_line_2d.png create mode 100644 tools/editor/icons/source/icon_line_2d.svg create mode 100644 tools/editor/plugins/line_2d_editor_plugin.cpp create mode 100644 tools/editor/plugins/line_2d_editor_plugin.h diff --git a/scene/2d/line_2d.cpp b/scene/2d/line_2d.cpp new file mode 100644 index 00000000000..9db36591336 --- /dev/null +++ b/scene/2d/line_2d.cpp @@ -0,0 +1,307 @@ +#include "line_2d.h" +#include "core_string_names.h" + + +// Needed so we can bind functions +VARIANT_ENUM_CAST(LineJointMode) +VARIANT_ENUM_CAST(LineCapMode) +VARIANT_ENUM_CAST(LineTextureMode) + + +Line2D::Line2D() : Node2D() { + _joint_mode = LINE_JOINT_SHARP; + _begin_cap_mode = LINE_CAP_NONE; + _end_cap_mode = LINE_CAP_NONE; + _width = 10; + _default_color = Color(0.4,0.5,1); + _sharp_limit = 2.f; + _round_precision = 8; +} + +void Line2D::set_points(const PoolVector &p_points) { + _points = p_points; + update(); +} + +void Line2D::set_width(float width) { + if(width < 0.0) + width = 0.0; + _width = width; + update(); +} + +float Line2D::get_width() const { + return _width; +} + +PoolVector Line2D::get_points() const { + return _points; +} + +void Line2D::set_point_pos(int i, Vector2 pos) { + _points.set(i, pos); + update(); +} + +Vector2 Line2D::get_point_pos(int i) const { + return _points.get(i); +} + +int Line2D::get_point_count() const { + return _points.size(); +} + +void Line2D::add_point(Vector2 pos) { + _points.append(pos); + update(); +} + +void Line2D::remove_point(int i) { + _points.remove(i); + update(); +} + +void Line2D::set_default_color(Color color) { + _default_color = color; + update(); +} + +Color Line2D::get_default_color() const { + return _default_color; +} + +void Line2D::set_gradient(const Ref& gradient) { + + // Cleanup previous connection if any + if(_gradient.is_valid()) { + (**_gradient).disconnect(CoreStringNames::get_singleton()->changed, this, "_gradient_changed"); + } + + _gradient = gradient; + + // Connect to the gradient so the line will update when the ColorRamp is changed + if(_gradient.is_valid()) { + (**_gradient).connect(CoreStringNames::get_singleton()->changed, this, "_gradient_changed"); + } + + update(); +} + +Ref Line2D::get_gradient() const { + return _gradient; +} + +void Line2D::set_texture(const Ref& texture) { + _texture = texture; + update(); +} + +Ref Line2D::get_texture() const { + return _texture; +} + +void Line2D::set_texture_mode(const LineTextureMode mode) { + _texture_mode = mode; + update(); +} + +LineTextureMode Line2D::get_texture_mode() const { + return _texture_mode; +} + +void Line2D::set_joint_mode(LineJointMode mode) { + _joint_mode = mode; + update(); +} + +LineJointMode Line2D::get_joint_mode() const { + return _joint_mode; +} + +void Line2D::set_begin_cap_mode(LineCapMode mode) { + _begin_cap_mode = mode; + update(); +} + +LineCapMode Line2D::get_begin_cap_mode() const { + return _begin_cap_mode; +} + +void Line2D::set_end_cap_mode(LineCapMode mode) { + _end_cap_mode = mode; + update(); +} + +LineCapMode Line2D::get_end_cap_mode() const { + return _end_cap_mode; +} + +void Line2D::_notification(int p_what) { + switch(p_what) { + case NOTIFICATION_DRAW: + _draw(); + break; + } +} + +void Line2D::set_sharp_limit(float limit) { + if(limit < 0.f) + limit = 0.f; + _sharp_limit = limit; + update(); +} + +float Line2D::get_sharp_limit() const { + return _sharp_limit; +} + +void Line2D::set_round_precision(int precision) { + if(precision < 1) + precision = 1; + _round_precision = precision; + update(); +} + +int Line2D::get_round_precision() const { + return _round_precision; +} + +void Line2D::_draw() { + if(_points.size() <= 1 || _width == 0.f) + return; + + // TODO Is this really needed? + // Copy points for faster access + Vector points; + points.resize(_points.size()); + int len = points.size(); { + PoolVector::Read points_read = _points.read(); + for(int i = 0; i < len; ++i) { + points[i] = points_read[i]; + } + } + + // TODO Maybe have it as member rather than copying parameters and allocating memory? + LineBuilder lb; + lb.points = points; + lb.default_color = _default_color; + lb.gradient = *_gradient; + lb.texture_mode = _texture_mode; + lb.joint_mode = _joint_mode; + lb.begin_cap_mode = _begin_cap_mode; + lb.end_cap_mode = _end_cap_mode; + lb.round_precision = _round_precision; + lb.sharp_limit = _sharp_limit; + lb.width = _width; + + lb.build(); + + RID texture_rid; + if(_texture.is_valid()) + texture_rid = (**_texture).get_rid(); + + VS::get_singleton()->canvas_item_add_triangle_array( + get_canvas_item(), + lb.indices, + lb.vertices, + lb.colors, + lb.uvs, + texture_rid); + + // DEBUG + // Draw wireframe +// if(lb.indices.size() % 3 == 0) { +// Color col(0,0,0); +// for(int i = 0; i < lb.indices.size(); i += 3) { +// int vi = lb.indices[i]; +// int lbvsize = lb.vertices.size(); +// Vector2 a = lb.vertices[lb.indices[i]]; +// Vector2 b = lb.vertices[lb.indices[i+1]]; +// Vector2 c = lb.vertices[lb.indices[i+2]]; +// draw_line(a, b, col); +// draw_line(b, c, col); +// draw_line(c, a, col); +// } +// for(int i = 0; i < lb.vertices.size(); ++i) { +// Vector2 p = lb.vertices[i]; +// draw_rect(Rect2(p.x-1, p.y-1, 2, 2), Color(0,0,0,0.5)); +// } +// } +} + +void Line2D::_gradient_changed() { + update(); +} + +// static +void Line2D::_bind_methods() { + + ClassDB::bind_method(_MD("set_points","points"), &Line2D::set_points); + ClassDB::bind_method(_MD("get_points"), &Line2D::get_points); + + ClassDB::bind_method(_MD("set_point_pos","i", "pos"), &Line2D::set_point_pos); + ClassDB::bind_method(_MD("get_point_pos", "i"), &Line2D::get_point_pos); + + ClassDB::bind_method(_MD("get_point_count"), &Line2D::get_point_count); + + ClassDB::bind_method(_MD("add_point", "pos"), &Line2D::add_point); + ClassDB::bind_method(_MD("remove_point", "i"), &Line2D::remove_point); + + ClassDB::bind_method(_MD("set_width","width"), &Line2D::set_width); + ClassDB::bind_method(_MD("get_width"), &Line2D::get_width); + + ClassDB::bind_method(_MD("set_default_color", "color"), &Line2D::set_default_color); + ClassDB::bind_method(_MD("get_default_color"), &Line2D::get_default_color); + + ClassDB::bind_method(_MD("set_gradient", "color"), &Line2D::set_gradient); + ClassDB::bind_method(_MD("get_gradient"), &Line2D::get_gradient); + + ClassDB::bind_method(_MD("set_texture", "texture"), &Line2D::set_texture); + ClassDB::bind_method(_MD("get_texture"), &Line2D::get_texture); + + ClassDB::bind_method(_MD("set_texture_mode", "mode"), &Line2D::set_texture_mode); + ClassDB::bind_method(_MD("get_texture_mode"), &Line2D::get_texture_mode); + + ClassDB::bind_method(_MD("set_joint_mode", "mode"), &Line2D::set_joint_mode); + ClassDB::bind_method(_MD("get_joint_mode"), &Line2D::get_joint_mode); + + ClassDB::bind_method(_MD("set_begin_cap_mode", "mode"), &Line2D::set_begin_cap_mode); + ClassDB::bind_method(_MD("get_begin_cap_mode"), &Line2D::get_begin_cap_mode); + + ClassDB::bind_method(_MD("set_end_cap_mode", "mode"), &Line2D::set_end_cap_mode); + ClassDB::bind_method(_MD("get_end_cap_mode"), &Line2D::get_end_cap_mode); + + ClassDB::bind_method(_MD("set_sharp_limit", "limit"), &Line2D::set_sharp_limit); + ClassDB::bind_method(_MD("get_sharp_limit"), &Line2D::get_sharp_limit); + + ClassDB::bind_method(_MD("set_round_precision", "precision"), &Line2D::set_round_precision); + ClassDB::bind_method(_MD("get_round_precision"), &Line2D::get_round_precision); + + ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR2_ARRAY, "points"), _SCS("set_points"), _SCS("get_points")); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "width"), _SCS("set_width"), _SCS("get_width")); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "default_color"), _SCS("set_default_color"), _SCS("get_default_color")); + ADD_PROPERTYNZ( PropertyInfo( Variant::OBJECT, "gradient", PROPERTY_HINT_RESOURCE_TYPE, "ColorRamp"), _SCS("set_gradient"), _SCS("get_gradient" ) ); + ADD_PROPERTYNZ( PropertyInfo( Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), _SCS("set_texture"), _SCS("get_texture" ) ); + ADD_PROPERTYNZ( PropertyInfo( Variant::INT, "texture_mode", PROPERTY_HINT_ENUM, "None,Tile" ), _SCS("set_texture_mode"),_SCS("get_texture_mode" ) ); + ADD_PROPERTYNZ( PropertyInfo( Variant::INT, "joint_mode", PROPERTY_HINT_ENUM, "Sharp,Bevel,Round" ), _SCS("set_joint_mode"),_SCS("get_joint_mode" ) ); + ADD_PROPERTYNZ( PropertyInfo( Variant::INT, "begin_cap_mode", PROPERTY_HINT_ENUM, "None,Box,Round" ), _SCS("set_begin_cap_mode"),_SCS("get_begin_cap_mode" ) ); + ADD_PROPERTYNZ( PropertyInfo( Variant::INT, "end_cap_mode", PROPERTY_HINT_ENUM, "None,Box,Round" ), _SCS("set_end_cap_mode"),_SCS("get_end_cap_mode" ) ); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "sharp_limit"), _SCS("set_sharp_limit"), _SCS("get_sharp_limit")); + ADD_PROPERTY(PropertyInfo(Variant::INT, "round_precision"), _SCS("set_round_precision"), _SCS("get_round_precision")); + + BIND_CONSTANT(LINE_JOINT_SHARP); + BIND_CONSTANT(LINE_JOINT_BEVEL); + BIND_CONSTANT(LINE_JOINT_ROUND); + + BIND_CONSTANT(LINE_CAP_NONE); + BIND_CONSTANT(LINE_CAP_BOX); + BIND_CONSTANT(LINE_CAP_ROUND); + + BIND_CONSTANT(LINE_TEXTURE_NONE); + BIND_CONSTANT(LINE_TEXTURE_TILE); + + ClassDB::bind_method(_MD("_gradient_changed"), &Line2D::_gradient_changed); + +} + + diff --git a/scene/2d/line_2d.h b/scene/2d/line_2d.h new file mode 100644 index 00000000000..f4e2eb7a55f --- /dev/null +++ b/scene/2d/line_2d.h @@ -0,0 +1,80 @@ +#ifndef LINE2D_H +#define LINE2D_H + +#include "node_2d.h" +#include "line_builder.h" + + +class Line2D : public Node2D { + + GDCLASS(Line2D, Node2D) + +public: + Line2D(); + + void set_points(const PoolVector & p_points); + PoolVector get_points() const; + + void set_point_pos(int i, Vector2 pos); + Vector2 get_point_pos(int i) const; + + int get_point_count() const; + + void add_point(Vector2 pos); + void remove_point(int i); + + void set_width(float width); + float get_width() const; + + void set_default_color(Color color); + Color get_default_color() const; + + void set_gradient(const Ref& gradient); + Ref get_gradient() const; + + void set_texture(const Ref& texture); + Ref get_texture() const; + + void set_texture_mode(const LineTextureMode mode); + LineTextureMode get_texture_mode() const; + + void set_joint_mode(LineJointMode mode); + LineJointMode get_joint_mode() const; + + void set_begin_cap_mode(LineCapMode mode); + LineCapMode get_begin_cap_mode() const; + + void set_end_cap_mode(LineCapMode mode); + LineCapMode get_end_cap_mode() const; + + void set_sharp_limit(float limit); + float get_sharp_limit() const; + + void set_round_precision(int precision); + int get_round_precision() const; + +protected: + void _notification(int p_what); + void _draw(); + + static void _bind_methods(); + +private: + void _gradient_changed(); + +private: + PoolVector _points; + LineJointMode _joint_mode; + LineCapMode _begin_cap_mode; + LineCapMode _end_cap_mode; + float _width; + Color _default_color; + Ref _gradient; + Ref _texture; + LineTextureMode _texture_mode; + float _sharp_limit; + int _round_precision; + +}; + +#endif // LINE2D_H diff --git a/scene/2d/line_builder.cpp b/scene/2d/line_builder.cpp new file mode 100644 index 00000000000..230f72cccdd --- /dev/null +++ b/scene/2d/line_builder.cpp @@ -0,0 +1,563 @@ +#include "line_builder.h" + +//---------------------------------------------------------------------------- +// Util +//---------------------------------------------------------------------------- + +enum SegmentIntersectionResult { + SEGMENT_PARALLEL = 0, + SEGMENT_NO_INTERSECT = 1, + SEGMENT_INTERSECT = 2 +}; + +static SegmentIntersectionResult segment_intersection( + Vector2 a, Vector2 b, Vector2 c, Vector2 d, + Vector2 * out_intersection) +{ + // http://paulbourke.net/geometry/pointlineplane/ <-- Good stuff + Vector2 cd = d - c; + Vector2 ab = b - a; + float div = cd.y*ab.x - cd.x*ab.y; + + if(Math::abs(div) > 0.001f) { + float ua = (cd.x * (a.y-c.y) - cd.y * (a.x-c.x)) / div; + float ub = (ab.x * (a.y-c.y) - ab.y * (a.x-c.x)) / div; + *out_intersection = a + ua * ab; + if(ua >= 0.f && ua <= 1.f && + ub >= 0.f && ub <= 1.f) + return SEGMENT_INTERSECT; + return SEGMENT_NO_INTERSECT; + } + + return SEGMENT_PARALLEL; +} + +// TODO I'm pretty sure there is an even faster way to swap things +template +static inline void swap(T & a, T & b) { + T tmp = a; + a = b; + b = tmp; +} + +static float calculate_total_distance(const Vector & points) { + float d = 0.f; + for(int i = 1; i < points.size(); ++i) { + d += points[i].distance_to(points[i-1]); + } + return d; +} + +static inline Vector2 rotate90(const Vector2 & v) { + // Note: the 2D referential is X-right, Y-down + return Vector2(v.y, -v.x); +} + +static inline Vector2 interpolate(const Rect2 & r, const Vector2 & v) { + return Vector2( + Math::lerp(r.get_pos().x, r.get_pos().x + r.get_size().x, v.x), + Math::lerp(r.get_pos().y, r.get_pos().y + r.get_size().y, v.y) + ); +} + +//---------------------------------------------------------------------------- +// LineBuilder +//---------------------------------------------------------------------------- + +LineBuilder::LineBuilder() { + joint_mode = LINE_JOINT_SHARP; + width = 10; + default_color = Color(0.4,0.5,1); + gradient = NULL; + sharp_limit = 2.f; + round_precision = 8; + begin_cap_mode = LINE_CAP_NONE; + end_cap_mode = LINE_CAP_NONE; + + _interpolate_color = false; + _last_index[0] = 0; + _last_index[1] = 0; +} + + +void LineBuilder::clear_output() { + vertices.clear(); + colors.clear(); + indices.clear(); +} + +void LineBuilder::build() { + + // Need at least 2 points to draw a line + if(points.size() < 2) { + clear_output(); + return; + } + + const float hw = width / 2.f; + const float hw_sq = hw*hw; + const float sharp_limit_sq = sharp_limit * sharp_limit; + const int len = points.size(); + + // Initial values + + Vector2 pos0 = points[0]; + Vector2 pos1 = points[1]; + Vector2 f0 = (pos1 - pos0).normalized(); + Vector2 u0 = rotate90(f0); + Vector2 pos_up0 = pos0 + u0 * hw; + Vector2 pos_down0 = pos0 - u0 * hw; + + Color color0; + Color color1; + + float current_distance0 = 0.f; + float current_distance1 = 0.f; + float total_distance; + _interpolate_color = gradient != NULL; + bool distance_required = _interpolate_color || texture_mode == LINE_TEXTURE_TILE; + if(distance_required) + total_distance = calculate_total_distance(points); + if(_interpolate_color) + color0 = gradient->get_color(0); + else + colors.push_back(default_color); + + float uvx0 = 0.f; + float uvx1 = 0.f; + + // Begin cap + if(begin_cap_mode == LINE_CAP_BOX) { + // Push back first vertices a little bit + pos_up0 -= f0 * hw; + pos_down0 -= f0 * hw; + // The line's outer length will be a little higher due to begin and end caps + total_distance += width; + current_distance0 += hw; + current_distance1 = current_distance0; + } + else if(begin_cap_mode == LINE_CAP_ROUND) { + if(texture_mode == LINE_TEXTURE_TILE) { + uvx0 = 0.5f; + } + new_arc(pos0, pos_up0 - pos0, -Math_PI, color0, Rect2(0.f, 0.f, 1.f, 1.f)); + total_distance += width; + current_distance0 += hw; + current_distance1 = current_distance0; + } + + strip_begin(pos_up0, pos_down0, color0, uvx0); + + // pos_up0 ------------- pos_up1 -------------------- + // | | + // pos0 - - - - - - - - - pos1 - - - - - - - - - pos2 + // | | + // pos_down0 ------------ pos_down1 ------------------ + // + // i-1 i i+1 + + // http://labs.hyperandroid.com/tag/opengl-lines + // (not the same implementation but visuals help a lot) + + // For each additional segment + for(int i = 1; i < len-1; ++i) { + + pos1 = points[i]; + Vector2 pos2 = points[i+1]; + + Vector2 f1 = (pos2 - pos1).normalized(); + Vector2 u1 = rotate90(f1); + + // Determine joint orientation + const float dp = u0.dot(f1); + const Orientation orientation = (dp > 0.f ? UP : DOWN); + + Vector2 inner_normal0, inner_normal1; + if(orientation == UP) { + inner_normal0 = u0 * hw; + inner_normal1 = u1 * hw; + } + else { + inner_normal0 = -u0 * hw; + inner_normal1 = -u1 * hw; + } + + // --------------------------- + // / + // 0 / 1 + // / / + // --------------------x------ / + // / / (here shown with orientation == DOWN) + // / / + // / / + // / / + // 2 / + // / + + // Find inner intersection at the joint + Vector2 corner_pos_in, corner_pos_out; + SegmentIntersectionResult intersection_result = segment_intersection( + pos0 + inner_normal0, pos1 + inner_normal0, + pos1 + inner_normal1, pos2 + inner_normal1, + &corner_pos_in); + + if(intersection_result == SEGMENT_INTERSECT) + // Inner parts of the segments intersect + corner_pos_out = 2.f * pos1 - corner_pos_in; + else { + // No intersection, segments are either parallel or too sharp + corner_pos_in = pos1 + inner_normal0; + corner_pos_out = pos1 - inner_normal0; + } + + Vector2 corner_pos_up, corner_pos_down; + if(orientation == UP) { + corner_pos_up = corner_pos_in; + corner_pos_down = corner_pos_out; + } + else { + corner_pos_up = corner_pos_out; + corner_pos_down = corner_pos_in; + } + + LineJointMode current_joint_mode = joint_mode; + + Vector2 pos_up1, pos_down1; + if(intersection_result == SEGMENT_INTERSECT) { + // Fallback on bevel if sharp angle is too high (because it would produce very long miters) + if(current_joint_mode == LINE_JOINT_SHARP && corner_pos_out.distance_squared_to(pos1) / hw_sq > sharp_limit_sq) { + current_joint_mode = LINE_JOINT_BEVEL; + } + if(current_joint_mode == LINE_JOINT_SHARP) { + // In this case, we won't create joint geometry, + // The previous and next line quads will directly share an edge. + pos_up1 = corner_pos_up; + pos_down1 = corner_pos_down; + } + else { + // Bevel or round + if(orientation == UP) { + pos_up1 = corner_pos_up; + pos_down1 = pos1 - u0 * hw; + } + else { + pos_up1 = pos1 + u0 * hw; + pos_down1 = corner_pos_down; + } + } + } + else { + // No intersection: fallback + pos_up1 = corner_pos_up; + pos_down1 = corner_pos_down; + } + + // Add current line body quad + // Triangles are clockwise + if(distance_required) { + current_distance1 += pos0.distance_to(pos1); + } + if(_interpolate_color) { + color1 = gradient->get_color_at_offset(current_distance1 / total_distance); + } + if(texture_mode == LINE_TEXTURE_TILE) { + uvx0 = current_distance0 / width; + uvx1 = current_distance1 / width; + } + + strip_add_quad(pos_up1, pos_down1, color1, uvx1); + + // Swap vars for use in the next line + color0 = color1; + u0 = u1; + f0 = f1; + pos0 = pos1; + current_distance0 = current_distance1; + if(intersection_result == SEGMENT_INTERSECT) { + if(current_joint_mode == LINE_JOINT_SHARP) { + pos_up0 = pos_up1; + pos_down0 = pos_down1; + } + else { + if(orientation == UP) { + pos_up0 = corner_pos_up; + pos_down0 = pos1 - u1 * hw; + } + else { + pos_up0 = pos1 + u1 * hw; + pos_down0 = corner_pos_down; + } + } + } + else { + pos_up0 = pos1 + u1 * hw; + pos_down0 = pos1 - u1 * hw; + } + // From this point, bu0 and bd0 concern the next segment + + // Add joint geometry + if(current_joint_mode != LINE_JOINT_SHARP) { + + // ________________ cbegin + // / \ + // / \ + // ____________/_ _ _\ cend + // | | + // | | + // | | + + Vector2 cbegin, cend; + if(orientation == UP) { + cbegin = pos_down1; + cend = pos_down0; + } + else { + cbegin = pos_up1; + cend = pos_up0; + } + + if(current_joint_mode == LINE_JOINT_BEVEL) { + strip_add_tri(cend, orientation); + } + else if(current_joint_mode == LINE_JOINT_ROUND) { + Vector2 vbegin = cbegin - pos1; + Vector2 vend = cend - pos1; + strip_add_arc(pos1, vend.angle_to(vbegin), orientation); + } + + if(intersection_result != SEGMENT_INTERSECT) + // In this case the joint is too fucked up to be re-used, + // start again the strip with fallback points + strip_begin(pos_up0, pos_down0, color1, uvx1); + } + } + + // Last (or only) segment + + pos1 = points[points.size()-1]; + + Vector2 pos_up1 = pos1 + u0 * hw; + Vector2 pos_down1 = pos1 - u0 * hw; + + // End cap (box) + if(end_cap_mode == LINE_CAP_BOX) { + pos_up1 += f0 * hw; + pos_down1 += f0 * hw; + } + + if(distance_required) { + current_distance1 += pos0.distance_to(pos1); + } + if(_interpolate_color) { + color1 = gradient->get_color(gradient->get_points_count()-1); + } + if(texture_mode == LINE_TEXTURE_TILE) { + uvx1 = current_distance1 / width; + } + + strip_add_quad(pos_up1, pos_down1, color1, uvx1); + + // End cap (round) + if(end_cap_mode == LINE_CAP_ROUND) { + // Note: color is not used in case we don't interpolate... + Color color = _interpolate_color ? gradient->get_color(gradient->get_points_count()-1) : Color(0,0,0); + new_arc(pos1, pos_up1 - pos1, Math_PI, color, Rect2(uvx1-0.5f, 0.f, 1.f, 1.f)); + } +} + +void LineBuilder::strip_begin(Vector2 up, Vector2 down, Color color, float uvx) { + int vi = vertices.size(); + + vertices.push_back(up); + vertices.push_back(down); + + if(_interpolate_color) { + colors.push_back(color); + colors.push_back(color); + } + + if(texture_mode != LINE_TEXTURE_NONE) { + uvs.push_back(Vector2(uvx, 0.f)); + uvs.push_back(Vector2(uvx, 1.f)); + } + + _last_index[UP] = vi; + _last_index[DOWN] = vi+1; +} + +void LineBuilder::strip_new_quad(Vector2 up, Vector2 down, Color color, float uvx) { + int vi = vertices.size(); + + vertices.push_back(vertices[_last_index[UP]]); + vertices.push_back(vertices[_last_index[DOWN]]); + vertices.push_back(up); + vertices.push_back(down); + + if(_interpolate_color) { + colors.push_back(color); + colors.push_back(color); + colors.push_back(color); + colors.push_back(color); + } + + if(texture_mode != LINE_TEXTURE_NONE) { + uvs.push_back(uvs[_last_index[UP]]); + uvs.push_back(uvs[_last_index[DOWN]]); + uvs.push_back(Vector2(uvx, UP)); + uvs.push_back(Vector2(uvx, DOWN)); + } + + indices.push_back(vi); + indices.push_back(vi+3); + indices.push_back(vi+1); + indices.push_back(vi); + indices.push_back(vi+2); + indices.push_back(vi+3); + + _last_index[UP] = vi+2; + _last_index[DOWN] = vi+3; +} + +void LineBuilder::strip_add_quad(Vector2 up, Vector2 down, Color color, float uvx) { + int vi = vertices.size(); + + vertices.push_back(up); + vertices.push_back(down); + + if(_interpolate_color) { + colors.push_back(color); + colors.push_back(color); + } + + if(texture_mode != LINE_TEXTURE_NONE) { + uvs.push_back(Vector2(uvx, 0.f)); + uvs.push_back(Vector2(uvx, 1.f)); + } + + indices.push_back(_last_index[UP]); + indices.push_back(vi+1); + indices.push_back(_last_index[DOWN]); + indices.push_back(_last_index[UP]); + indices.push_back(vi); + indices.push_back(vi+1); + + _last_index[UP] = vi; + _last_index[DOWN] = vi+1; +} + +void LineBuilder::strip_add_tri(Vector2 up, Orientation orientation) { + int vi = vertices.size(); + + vertices.push_back(up); + + if(_interpolate_color) { + colors.push_back(colors[colors.size()-1]); + } + + Orientation opposite_orientation = orientation == UP ? DOWN : UP; + + if(texture_mode != LINE_TEXTURE_NONE) { + // UVs are just one slice of the texture all along + // (otherwise we can't share the bottom vertice) + uvs.push_back(uvs[_last_index[opposite_orientation]]); + } + + indices.push_back(_last_index[opposite_orientation]); + indices.push_back(vi); + indices.push_back(_last_index[orientation]); + + _last_index[opposite_orientation] = vi; +} + +void LineBuilder::strip_add_arc(Vector2 center, float angle_delta, Orientation orientation) { + + // Take the two last vertices and extrude an arc made of triangles + // that all share one of the initial vertices + + Orientation opposite_orientation = orientation == UP ? DOWN : UP; + Vector2 vbegin = vertices[_last_index[opposite_orientation]] - center; + float radius = vbegin.length(); + float angle_step = Math_PI / static_cast(round_precision); + float steps = Math::abs(angle_delta) / angle_step; + + if(angle_delta < 0.f) + angle_step = -angle_step; + + float t = vbegin.angle_to(Vector2(1, 0)); + float end_angle = t + angle_delta; + Vector2 rpos(0,0); + + // Arc vertices + for(int ti = 0; ti < steps; ++ti, t += angle_step) { + rpos = center + Vector2(Math::cos(t), Math::sin(t)) * radius; + strip_add_tri(rpos, orientation); + } + + // Last arc vertice + rpos = center + Vector2(Math::cos(end_angle), Math::sin(end_angle)) * radius; + strip_add_tri(rpos, orientation); +} + +void LineBuilder::new_arc(Vector2 center, Vector2 vbegin, float angle_delta, Color color, Rect2 uv_rect) { + + // Make a standalone arc that doesn't use existing vertices, + // with undistorted UVs from withing a square section + + float radius = vbegin.length(); + float angle_step = Math_PI / static_cast(round_precision); + float steps = Math::abs(angle_delta) / angle_step; + + if(angle_delta < 0.f) + angle_step = -angle_step; + + float t = vbegin.angle_to(Vector2(1, 0)); + float end_angle = t + angle_delta; + Vector2 rpos(0,0); + float tt_begin = -Math_PI / 2.f; + float tt = tt_begin; + + // Center vertice + int vi = vertices.size(); + vertices.push_back(center); + if(_interpolate_color) + colors.push_back(color); + if(texture_mode != LINE_TEXTURE_NONE) + uvs.push_back(interpolate(uv_rect, Vector2(0.5f, 0.5f))); + + // Arc vertices + for(int ti = 0; ti < steps; ++ti, t += angle_step) { + Vector2 sc = Vector2(Math::cos(t), Math::sin(t)); + rpos = center + sc * radius; + + vertices.push_back(rpos); + if(_interpolate_color) + colors.push_back(color); + if(texture_mode != LINE_TEXTURE_NONE) { + Vector2 tsc = Vector2(Math::cos(tt), Math::sin(tt)); + uvs.push_back(interpolate(uv_rect, 0.5f*(tsc+Vector2(1.f,1.f)))); + tt += angle_step; + } + } + + // Last arc vertice + Vector2 sc = Vector2(Math::cos(end_angle), Math::sin(end_angle)); + rpos = center + sc * radius; + vertices.push_back(rpos); + if(_interpolate_color) + colors.push_back(color); + if(texture_mode != LINE_TEXTURE_NONE) { + tt = tt_begin + angle_delta; + Vector2 tsc = Vector2(Math::cos(tt), Math::sin(tt)); + uvs.push_back(interpolate(uv_rect, 0.5f*(tsc+Vector2(1.f,1.f)))); + } + + // Make up triangles + int vi0 = vi; + for(int ti = 0; ti < steps; ++ti) { + indices.push_back(vi0); + indices.push_back(++vi); + indices.push_back(vi+1); + } +} + + diff --git a/scene/2d/line_builder.h b/scene/2d/line_builder.h new file mode 100644 index 00000000000..73419f88d82 --- /dev/null +++ b/scene/2d/line_builder.h @@ -0,0 +1,76 @@ +#ifndef LINE_BUILDER_H +#define LINE_BUILDER_H + +#include "math_2d.h" +#include "color.h" +#include "scene/resources/color_ramp.h" + +enum LineJointMode { + LINE_JOINT_SHARP = 0, + LINE_JOINT_BEVEL, + LINE_JOINT_ROUND +}; + +enum LineCapMode { + LINE_CAP_NONE = 0, + LINE_CAP_BOX, + LINE_CAP_ROUND +}; + +enum LineTextureMode { + LINE_TEXTURE_NONE = 0, + LINE_TEXTURE_TILE + // TODO STRETCH mode +}; + +class LineBuilder { +public: + // TODO Move in a struct and reference it + // Input + Vector points; + LineJointMode joint_mode; + LineCapMode begin_cap_mode; + LineCapMode end_cap_mode; + float width; + Color default_color; + ColorRamp* gradient; + LineTextureMode texture_mode; + float sharp_limit; + int round_precision; + // TODO offset_joints option (offers alternative implementation of round joints) + + // TODO Move in a struct and reference it + // Output + Vector vertices; + Vector colors; + Vector uvs; + Vector indices; + + LineBuilder(); + + void build(); + void clear_output(); + +private: + enum Orientation { + UP = 0, + DOWN = 1 + }; + + // Triangle-strip methods + void strip_begin(Vector2 up, Vector2 down, Color color, float uvx); + void strip_new_quad(Vector2 up, Vector2 down, Color color, float uvx); + void strip_add_quad(Vector2 up, Vector2 down, Color color, float uvx); + void strip_add_tri(Vector2 up, Orientation orientation); + void strip_add_arc(Vector2 center, float angle_delta, Orientation orientation); + + void new_arc(Vector2 center, Vector2 vbegin, float angle_delta, Color color, Rect2 uv_rect); + +private: + bool _interpolate_color; + int _last_index[2]; // Index of last up and down vertices of the strip + +}; + + +#endif // LINE_BUILDER_H diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index f68e5ca9599..d93fecffe12 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -120,6 +120,8 @@ #include "scene/2d/position_2d.h" #include "scene/2d/tile_map.h" //#include "scene/2d/tile_map.h" +#include "scene/2d/line_2d.h" + #include "scene/resources/tile_set.h" #include "scene/animation/animation_player.h" @@ -498,6 +500,7 @@ void register_scene_types() { ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_virtual_class(); ClassDB::register_virtual_class(); ClassDB::register_class(); diff --git a/scene/resources/color_ramp.cpp b/scene/resources/color_ramp.cpp index 1144ea41f10..c577e3e4440 100644 --- a/scene/resources/color_ramp.cpp +++ b/scene/resources/color_ramp.cpp @@ -27,6 +27,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ #include "color_ramp.h" +#include "core_string_names.h" //setter and getter names for property serialization #define COLOR_RAMP_GET_OFFSETS "get_offsets" @@ -104,6 +105,7 @@ void ColorRamp::set_offsets(const Vector& p_offsets) { points[i].offset = p_offsets[i]; } is_sorted = false; + emit_signal(CoreStringNames::get_singleton()->changed); } void ColorRamp::set_colors(const Vector& p_colors) { @@ -114,6 +116,7 @@ void ColorRamp::set_colors(const Vector& p_colors) { { points[i].color = p_colors[i]; } + emit_signal(CoreStringNames::get_singleton()->changed); } Vector& ColorRamp::get_points() { @@ -128,6 +131,7 @@ void ColorRamp::add_point(float p_offset, const Color& p_color) { is_sorted=false; points.push_back(p); + emit_signal(CoreStringNames::get_singleton()->changed); } void ColorRamp::remove_point(int p_index) { @@ -135,11 +139,13 @@ void ColorRamp::remove_point(int p_index) { ERR_FAIL_INDEX(p_index,points.size()); ERR_FAIL_COND(points.size()<=2); points.remove(p_index); + emit_signal(CoreStringNames::get_singleton()->changed); } void ColorRamp::set_points(Vector& p_points) { points = p_points; is_sorted = false; + emit_signal(CoreStringNames::get_singleton()->changed); } void ColorRamp::set_offset(int pos, const float offset) { @@ -147,6 +153,7 @@ void ColorRamp::set_offset(int pos, const float offset) { points.resize(pos + 1); points[pos].offset = offset; is_sorted = false; + emit_signal(CoreStringNames::get_singleton()->changed); } float ColorRamp::get_offset(int pos) const { @@ -162,6 +169,7 @@ void ColorRamp::set_color(int pos, const Color& color) { is_sorted = false; } points[pos].color = color; + emit_signal(CoreStringNames::get_singleton()->changed); } Color ColorRamp::get_color(int pos) const { diff --git a/tools/editor/editor_node.cpp b/tools/editor/editor_node.cpp index bb33f19a2b7..979f54dabb5 100644 --- a/tools/editor/editor_node.cpp +++ b/tools/editor/editor_node.cpp @@ -88,6 +88,7 @@ #include "plugins/script_editor_plugin.h" #include "plugins/script_text_editor.h" #include "plugins/path_2d_editor_plugin.h" +#include "plugins/line_2d_editor_plugin.h" #include "plugins/particles_editor_plugin.h" #include "plugins/particles_2d_editor_plugin.h" #include "plugins/animation_tree_editor_plugin.h" @@ -6599,6 +6600,7 @@ EditorNode::EditorNode() { add_editor_plugin( memnew( Path2DEditorPlugin(this) ) ); //add_editor_plugin( memnew( PathEditorPlugin(this) ) ); //add_editor_plugin( memnew( BakedLightEditorPlugin(this) ) ); + add_editor_plugin( memnew( Line2DEditorPlugin(this) ) ); add_editor_plugin( memnew( Polygon2DEditorPlugin(this) ) ); add_editor_plugin( memnew( LightOccluder2DEditorPlugin(this) ) ); add_editor_plugin( memnew( NavigationPolygonEditorPlugin(this) ) ); diff --git a/tools/editor/icons/2x/icon_line_2d.png b/tools/editor/icons/2x/icon_line_2d.png new file mode 100644 index 0000000000000000000000000000000000000000..27299a2b69d62b1b4c5da387e1edb84b89fa3f61 GIT binary patch literal 795 zcmV+$1LXXPP)leK~z|U?UcVyQ&AMhKj*cDm=-Cp5Ec_wGxDaA1 zi6N4R0pcH^|AB)xfy6-xp<=qY@T2}*;!&-b2p-~#_Uq~5^!WG-x@(*Pfy*A-<5lipTry zm~83%UZMNpy|(me^VRNvcq{BLqYfA95Kv+NnqymJa&Bqsazg|pK5w@o+;ET{hJbB@ zsrVyQRpyp5kK)PfMm(9_m`~=)MbspqaNxTQ_zWr)xgA(l)tBZu(}nor&Xsq`TvEiE zM(IN7veBu!1PBs#ezSPE%ExfIAks7uU)~+X<68x89+&gf0ul-Js1Pza+hZ*NL{Q81DIJ#Ujs}>3O;Y) zlRH^-pd(`;BM6%ao2vHXYE_$XjN*7Q`vzs$l+fdZg4HN)}{h*daO8>QmjGEK$0v9+7 Z{01dB@GktQ)7=07002ovPDHLkV1lj;W}W~5 literal 0 HcmV?d00001 diff --git a/tools/editor/icons/icon_line_2d.png b/tools/editor/icons/icon_line_2d.png new file mode 100644 index 0000000000000000000000000000000000000000..4ebf46af04e516f3e8e776ca7812d45ec07b9fc7 GIT binary patch literal 474 zcmV<00VV#4P)rGLSDUJfrk@ z3HK0fTnmy^A=W%$|EPSme_X!VIeoKyUyVuscB2@izZzsQ7T8|%I_zPJ9_i5RL%iA6DLTD)S1qh&h)X@4Ww!!QOeTS3K_y=mZXcX;mI(({?I4S^+%(nv Q#{d8T07*qoM6N<$g4Son0ssI2 literal 0 HcmV?d00001 diff --git a/tools/editor/icons/source/icon_line_2d.svg b/tools/editor/icons/source/icon_line_2d.svg new file mode 100644 index 00000000000..7f833f4a9cd --- /dev/null +++ b/tools/editor/icons/source/icon_line_2d.svg @@ -0,0 +1,82 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/tools/editor/plugins/line_2d_editor_plugin.cpp b/tools/editor/plugins/line_2d_editor_plugin.cpp new file mode 100644 index 00000000000..054a2c62e0f --- /dev/null +++ b/tools/editor/plugins/line_2d_editor_plugin.cpp @@ -0,0 +1,283 @@ +#include "line_2d_editor_plugin.h" + +#include "canvas_item_editor_plugin.h" +#include "os/file_access.h" +#include "tools/editor/editor_settings.h" +#include "os/keyboard.h" + + +//---------------------------------------------------------------------------- +// Line2DEditor +//---------------------------------------------------------------------------- + +void Line2DEditor::_node_removed(Node *p_node) { + if(p_node == node) { + node=NULL; + hide(); + } +} + +void Line2DEditor::_notification(int p_what) { + switch(p_what) { + case NOTIFICATION_VISIBILITY_CHANGED: + // This widget is not a child but should have the same visibility state + base_hb->set_visible(is_visible()); + break; + } +} + +Vector2 Line2DEditor::mouse_to_local_pos(Vector2 gpoint, bool alt) { + Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_global_transform(); + return !alt? canvas_item_editor->snap_point(xform.affine_inverse().xform(gpoint)) + : node->get_global_transform().affine_inverse().xform( + canvas_item_editor->snap_point( + canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint)) ); +} + +int Line2DEditor::get_point_index_at(Vector2 gpos) { + ERR_FAIL_COND_V(node == 0, -1); + + real_t grab_treshold = EDITOR_DEF("poly_editor/point_grab_radius", 8); + Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_global_transform(); + + for(int i = 0; i < node->get_point_count(); ++i) { + Point2 p = xform.xform( node->get_point_pos(i) ); + if(gpos.distance_to(p) < grab_treshold) { + return i; + } + } + + return -1; +} + +bool Line2DEditor::forward_input_event(const InputEvent& p_event) { + + if (!node) + return false; + + if (!node->is_visible()) + return false; + + switch(p_event.type) { + + case InputEvent::MOUSE_BUTTON: { + + const InputEventMouseButton &mb = p_event.mouse_button; + + Vector2 gpoint = Point2(mb.x,mb.y); + Vector2 cpoint = mouse_to_local_pos(gpoint, mb.mod.alt); + + if(mb.pressed && _dragging == false) { + int i = get_point_index_at(gpoint); + if(i != -1) { + if (mb.button_index == BUTTON_LEFT && !mb.mod.shift && mode == MODE_EDIT) { + _dragging = true; + action_point = i; + moving_from = node->get_point_pos(i); + moving_screen_from = gpoint; + } + else if((mb.button_index == BUTTON_RIGHT && mode == MODE_EDIT) || (mb.button_index == BUTTON_LEFT && mode == MODE_DELETE)) { + undo_redo->create_action(TTR("Remove Point from Line2D")); + undo_redo->add_do_method(node, "remove_point", i); + undo_redo->add_undo_method(node, "add_point", node->get_point_pos(i), i); + undo_redo->add_do_method(canvas_item_editor->get_viewport_control(), "update"); + undo_redo->add_undo_method(canvas_item_editor->get_viewport_control(), "update"); + undo_redo->commit_action(); + } + } + return true; + } + + if(mb.pressed && mb.button_index == BUTTON_LEFT && ((mb.mod.command && mode == MODE_EDIT) || mode == MODE_CREATE)) { + + undo_redo->create_action(TTR("Add Point to Line2D")); + undo_redo->add_do_method(node, "add_point", cpoint); + undo_redo->add_undo_method(node, "remove_point", node->get_point_count()); + undo_redo->add_do_method(canvas_item_editor->get_viewport_control(), "update"); + undo_redo->add_undo_method(canvas_item_editor->get_viewport_control(), "update"); + undo_redo->commit_action(); + + _dragging = true; + action_point = node->get_point_count()-1; + moving_from = node->get_point_pos(action_point); + moving_screen_from = gpoint; + + canvas_item_editor->get_viewport_control()->update(); + + return true; + } + + if(!mb.pressed && mb.button_index == BUTTON_LEFT && _dragging) { + undo_redo->create_action(TTR("Move Point in Line2D")); + undo_redo->add_do_method(node, "set_point_pos", action_point, cpoint); + undo_redo->add_undo_method(node, "set_point_pos", action_point, moving_from); + undo_redo->add_do_method(canvas_item_editor->get_viewport_control(), "update"); + undo_redo->add_undo_method(canvas_item_editor->get_viewport_control(), "update"); + undo_redo->commit_action(); + _dragging = false; + return true; + } + } + break; + + case InputEvent::MOUSE_MOTION: { + if (_dragging) { + const InputEventMouseMotion &mm = p_event.mouse_motion; + Vector2 cpoint = mouse_to_local_pos(Vector2(mm.x, mm.y), mm.mod.alt); + node->set_point_pos(action_point,cpoint); + canvas_item_editor->get_viewport_control()->update(); + return true; + } + } + break; + } + + return false; +} + +void Line2DEditor::_canvas_draw() { + + if (!node) + return; + + if (!node->is_visible()) + return; + + Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_global_transform(); + Ref handle = get_icon("EditorHandle", "EditorIcons"); + Size2 handle_size = handle->get_size(); + + int len = node->get_point_count(); + Control *vpc = canvas_item_editor->get_viewport_control(); + + for(int i=0; i < len; ++i) { + Vector2 point = xform.xform(node->get_point_pos(i)); + vpc->draw_texture_rect(handle, Rect2(point - handle_size * 0.5, handle_size), false); + } +} + +void Line2DEditor::_node_visibility_changed() { + if (!node) + return; + canvas_item_editor->get_viewport_control()->update(); +} + +void Line2DEditor::edit(Node *p_line2d) { + + if (!canvas_item_editor) + canvas_item_editor = CanvasItemEditor::get_singleton(); + + if (p_line2d) { + node = p_line2d->cast_to(); + if (!canvas_item_editor->get_viewport_control()->is_connected("draw", this, "_canvas_draw")) + canvas_item_editor->get_viewport_control()->connect("draw", this, "_canvas_draw"); + if (!node->is_connected("visibility_changed", this, "_node_visibility_changed")) + node->connect("visibility_changed", this, "_node_visibility_changed"); + } + else { + if (canvas_item_editor->get_viewport_control()->is_connected("draw", this, "_canvas_draw")) + canvas_item_editor->get_viewport_control()->disconnect("draw", this, "_canvas_draw"); + // node may have been deleted at this point + if (node && node->is_connected("visibility_changed", this, "_node_visibility_changed")) + node->disconnect("visibility_changed", this, "_node_visibility_changed"); + node = NULL; + } +} + +void Line2DEditor::_bind_methods() { + ClassDB::bind_method(_MD("_canvas_draw"), &Line2DEditor::_canvas_draw); + ClassDB::bind_method(_MD("_node_visibility_changed"), &Line2DEditor::_node_visibility_changed); + ClassDB::bind_method(_MD("_mode_selected"), &Line2DEditor::_mode_selected); +} + +void Line2DEditor::_mode_selected(int p_mode) { + for(unsigned int i = 0; i < _MODE_COUNT; ++i) { + toolbar_buttons[i]->set_pressed(i == p_mode); + } + mode = Mode(p_mode); +} + +Line2DEditor::Line2DEditor(EditorNode *p_editor) { + + canvas_item_editor = NULL; + editor = p_editor; + undo_redo = editor->get_undo_redo(); + + _dragging = false; + + base_hb = memnew( HBoxContainer ); + CanvasItemEditor::get_singleton()->add_control_to_menu_panel(base_hb); + + sep = memnew( VSeparator); + base_hb->add_child(sep); + + { + ToolButton * b = memnew(ToolButton); + b->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("CurveEdit", "EditorIcons")); + b->set_toggle_mode(true); + b->set_focus_mode(Control::FOCUS_NONE); + b->set_tooltip( + TTR("Select Points")+"\n" + + TTR("Shift+Drag: Select Control Points")+"\n" + + keycode_get_string(KEY_MASK_CMD) + + TTR("Click: Add Point")+"\n" + + TTR("Right Click: Delete Point")); + b->connect("pressed", this, "_mode_selected", varray(MODE_EDIT)); + toolbar_buttons[MODE_EDIT] = b; + base_hb->add_child(b); + } + + { + ToolButton * b = memnew(ToolButton); + b->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("CurveCreate", "EditorIcons")); + b->set_toggle_mode(true); + b->set_focus_mode(Control::FOCUS_NONE); + b->set_tooltip(TTR("Add Point (in empty space)")+"\n"+TTR("Split Segment (in line)")); + b->connect("pressed", this, "_mode_selected", varray(MODE_CREATE)); + toolbar_buttons[MODE_CREATE] = b; + base_hb->add_child(b); + } + + { + ToolButton * b = memnew( ToolButton ); + b->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("CurveDelete", "EditorIcons")); + b->set_toggle_mode(true); + b->set_focus_mode(Control::FOCUS_NONE); + b->set_tooltip(TTR("Delete Point")); + b->connect("pressed", this, "_mode_selected", varray(MODE_DELETE)); + toolbar_buttons[MODE_DELETE] = b; + base_hb->add_child(b); + } + + base_hb->hide(); + hide(); + + _mode_selected(MODE_CREATE); +} + +//---------------------------------------------------------------------------- +// Line2DEditorPlugin +//---------------------------------------------------------------------------- + +void Line2DEditorPlugin::edit(Object *p_object) { + line2d_editor->edit(p_object->cast_to()); +} + +bool Line2DEditorPlugin::handles(Object *p_object) const { + return p_object->is_class("Line2D"); +} + +void Line2DEditorPlugin::make_visible(bool p_visible) { + line2d_editor->set_visible(p_visible); + if(p_visible == false) + line2d_editor->edit(NULL); +} + +Line2DEditorPlugin::Line2DEditorPlugin(EditorNode *p_node) { + editor=p_node; + line2d_editor = memnew( Line2DEditor(p_node) ); + CanvasItemEditor::get_singleton()->add_control_to_menu_panel(line2d_editor); + line2d_editor->hide(); +} + + diff --git a/tools/editor/plugins/line_2d_editor_plugin.h b/tools/editor/plugins/line_2d_editor_plugin.h new file mode 100644 index 00000000000..0df64208a88 --- /dev/null +++ b/tools/editor/plugins/line_2d_editor_plugin.h @@ -0,0 +1,85 @@ +#ifndef LINE_2D_EDITOR_PLUGIN_H +#define LINE_2D_EDITOR_PLUGIN_H + +#include "tools/editor/editor_plugin.h" +#include "tools/editor/editor_node.h" +#include "scene/2d/path_2d.h" +#include "scene/gui/tool_button.h" +#include "scene/gui/button_group.h" +#include "scene/2d/line_2d.h" + + +class CanvasItemEditor; + +class Line2DEditor : public HBoxContainer { + GDCLASS(Line2DEditor, HBoxContainer) + +public: + bool forward_input_event(const InputEvent& p_event); + void edit(Node *p_line2d); + Line2DEditor(EditorNode *p_editor); + +protected: + void _node_removed(Node *p_node); + void _notification(int p_what); + + Vector2 mouse_to_local_pos(Vector2 mpos); + + static void _bind_methods(); + +private: + void _mode_selected(int p_mode); + void _canvas_draw(); + void _node_visibility_changed(); + + int get_point_index_at(Vector2 gpos); + Vector2 mouse_to_local_pos(Vector2 gpos, bool alt); + + UndoRedo *undo_redo; + + CanvasItemEditor *canvas_item_editor; + EditorNode *editor; + Panel *panel; + Line2D *node; + + HBoxContainer *base_hb; + Separator *sep; + + enum Mode { + MODE_CREATE = 0, + MODE_EDIT, + MODE_DELETE, + _MODE_COUNT + }; + + Mode mode; + ToolButton* toolbar_buttons[_MODE_COUNT]; + + bool _dragging; + int action_point; + Point2 moving_from; + Point2 moving_screen_from; +}; + +class Line2DEditorPlugin : public EditorPlugin { + GDCLASS( Line2DEditorPlugin, EditorPlugin ) + +public: + virtual bool forward_canvas_input_event(const Transform2D& p_canvas_xform,const InputEvent& p_event) { return line2d_editor->forward_input_event(p_event); } + + virtual String get_name() const { return "Line2D"; } + bool has_main_screen() const { return false; } + virtual void edit(Object *p_node); + virtual bool handles(Object *p_node) const; + virtual void make_visible(bool p_visible); + + Line2DEditorPlugin(EditorNode *p_node); + +private: + Line2DEditor *line2d_editor; + EditorNode *editor; + +}; + +#endif // LINE_2D_EDITOR_PLUGIN_H +