diff options
author | RĂ©mi Verschelde <rverschelde@gmail.com> | 2017-02-12 23:30:04 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-02-12 23:30:04 +0100 |
commit | 117a83fcb916cb02777dea73fb642216fd2e1d79 (patch) | |
tree | 1c50d003af87ab2fe00a73ef23e90e7ef97fdb3e /scene/2d | |
parent | 37e75873ef88443a565d371ec638f554d92d4293 (diff) | |
parent | e2fba10b952f694f604e20b9e5f220023a5f8fd2 (diff) |
Merge pull request #7352 from Zylann/polyline
Polyline
Diffstat (limited to 'scene/2d')
-rw-r--r-- | scene/2d/line_2d.cpp | 307 | ||||
-rw-r--r-- | scene/2d/line_2d.h | 80 | ||||
-rw-r--r-- | scene/2d/line_builder.cpp | 563 | ||||
-rw-r--r-- | scene/2d/line_builder.h | 76 |
4 files changed, 1026 insertions, 0 deletions
diff --git a/scene/2d/line_2d.cpp b/scene/2d/line_2d.cpp new file mode 100644 index 0000000000..9db3659133 --- /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<Vector2> &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<Vector2> 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<ColorRamp>& 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<ColorRamp> Line2D::get_gradient() const { + return _gradient; +} + +void Line2D::set_texture(const Ref<Texture>& texture) { + _texture = texture; + update(); +} + +Ref<Texture> 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<Vector2> points; + points.resize(_points.size()); + int len = points.size(); { + PoolVector<Vector2>::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 0000000000..f4e2eb7a55 --- /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<Vector2> & p_points); + PoolVector<Vector2> 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<ColorRamp>& gradient); + Ref<ColorRamp> get_gradient() const; + + void set_texture(const Ref<Texture>& texture); + Ref<Texture> 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<Vector2> _points; + LineJointMode _joint_mode; + LineCapMode _begin_cap_mode; + LineCapMode _end_cap_mode; + float _width; + Color _default_color; + Ref<ColorRamp> _gradient; + Ref<Texture> _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 0000000000..230f72cccd --- /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 <typename T> +static inline void swap(T & a, T & b) { + T tmp = a; + a = b; + b = tmp; +} + +static float calculate_total_distance(const Vector<Vector2> & 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<float>(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<float>(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 0000000000..73419f88d8 --- /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<Vector2> 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<Vector2> vertices; + Vector<Color> colors; + Vector<Vector2> uvs; + Vector<int> 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 |