Merge pull request #79182 from MewPurPur/polyline-close

Add closed property to Line2D
This commit is contained in:
Rémi Verschelde 2023-09-16 21:20:47 +02:00
commit 4c01c62233
No known key found for this signature in database
GPG key ID: C3336907360768E1
5 changed files with 221 additions and 215 deletions

View file

@ -63,16 +63,21 @@
[b]Note:[/b] [Line2D] is not accelerated by batching when being anti-aliased. [b]Note:[/b] [Line2D] is not accelerated by batching when being anti-aliased.
</member> </member>
<member name="begin_cap_mode" type="int" setter="set_begin_cap_mode" getter="get_begin_cap_mode" enum="Line2D.LineCapMode" default="0"> <member name="begin_cap_mode" type="int" setter="set_begin_cap_mode" getter="get_begin_cap_mode" enum="Line2D.LineCapMode" default="0">
The style of the beginning of the polyline. Use [enum LineCapMode] constants. The style of the beginning of the polyline, if [member closed] is [code]false[/code]. Use [enum LineCapMode] constants.
</member>
<member name="closed" type="bool" setter="set_closed" getter="is_closed" default="false">
If [code]true[/code] and the polyline has more than 2 points, the last point and the first one will be connected by a segment.
[b]Note:[/b] The shape of the closing segment is not guaranteed to be seamless if a [member width_curve] is provided.
[b]Note:[/b] The joint between the closing segment and the first segment is drawn first and it samples the [member gradient] and the [member width_curve] at the beginning. This is an implementation detail that might change in a future version.
</member> </member>
<member name="default_color" type="Color" setter="set_default_color" getter="get_default_color" default="Color(1, 1, 1, 1)"> <member name="default_color" type="Color" setter="set_default_color" getter="get_default_color" default="Color(1, 1, 1, 1)">
The color of the polyline. Will not be used if a gradient is set. The color of the polyline. Will not be used if a gradient is set.
</member> </member>
<member name="end_cap_mode" type="int" setter="set_end_cap_mode" getter="get_end_cap_mode" enum="Line2D.LineCapMode" default="0"> <member name="end_cap_mode" type="int" setter="set_end_cap_mode" getter="get_end_cap_mode" enum="Line2D.LineCapMode" default="0">
The style of the end of the polyline. Use [enum LineCapMode] constants. The style of the end of the polyline, if [member closed] is [code]false[/code]. Use [enum LineCapMode] constants.
</member> </member>
<member name="gradient" type="Gradient" setter="set_gradient" getter="get_gradient"> <member name="gradient" type="Gradient" setter="set_gradient" getter="get_gradient">
The gradient is drawn through the whole line from start to finish. The default color will not be used if a gradient is set. The gradient is drawn through the whole line from start to finish. The [member default_color] will not be used if this property is set.
</member> </member>
<member name="joint_mode" type="int" setter="set_joint_mode" getter="get_joint_mode" enum="Line2D.LineJointMode" default="0"> <member name="joint_mode" type="int" setter="set_joint_mode" getter="get_joint_mode" enum="Line2D.LineJointMode" default="0">
The style of the connections between segments of the polyline. Use [enum LineJointMode] constants. The style of the connections between segments of the polyline. Use [enum LineJointMode] constants.
@ -93,7 +98,7 @@
The style to render the [member texture] of the polyline. Use [enum LineTextureMode] constants. The style to render the [member texture] of the polyline. Use [enum LineTextureMode] constants.
</member> </member>
<member name="width" type="float" setter="set_width" getter="get_width" default="10.0"> <member name="width" type="float" setter="set_width" getter="get_width" default="10.0">
The polyline's width The polyline's width.
</member> </member>
<member name="width_curve" type="Curve" setter="set_curve" getter="get_curve"> <member name="width_curve" type="Curve" setter="set_curve" getter="get_curve">
The polyline's width curve. The width of the polyline over its length will be equivalent to the value of the width curve over its domain. The polyline's width curve. The width of the polyline over its length will be equivalent to the value of the width curve over its domain.

View file

@ -42,12 +42,12 @@ Rect2 Line2D::_edit_get_rect() const {
return Rect2(0, 0, 0, 0); return Rect2(0, 0, 0, 0);
} }
Vector2 d = Vector2(_width, _width); Vector2 d = Vector2(_width, _width);
Rect2 aabb = Rect2(_points[0] - d, 2 * d); Rect2 bounding_rect = Rect2(_points[0] - d, 2 * d);
for (int i = 1; i < _points.size(); i++) { for (int i = 1; i < _points.size(); i++) {
aabb.expand_to(_points[i] - d); bounding_rect.expand_to(_points[i] - d);
aabb.expand_to(_points[i] + d); bounding_rect.expand_to(_points[i] + d);
} }
return aabb; return bounding_rect;
} }
bool Line2D::_edit_use_rect() const { bool Line2D::_edit_use_rect() const {
@ -59,7 +59,14 @@ bool Line2D::_edit_is_selected_on_click(const Point2 &p_point, double p_toleranc
const Vector2 *points = _points.ptr(); const Vector2 *points = _points.ptr();
for (int i = 0; i < _points.size() - 1; i++) { for (int i = 0; i < _points.size() - 1; i++) {
Vector2 p = Geometry2D::get_closest_point_to_segment(p_point, &points[i]); Vector2 p = Geometry2D::get_closest_point_to_segment(p_point, &points[i]);
if (p.distance_to(p_point) <= d) { if (p_point.distance_to(p) <= d) {
return true;
}
}
if (_closed && _points.size() > 2) {
const Vector2 closing_segment[2] = { points[0], points[_points.size() - 1] };
Vector2 p = Geometry2D::get_closest_point_to_segment(p_point, closing_segment);
if (p_point.distance_to(p) <= d) {
return true; return true;
} }
} }
@ -73,6 +80,15 @@ void Line2D::set_points(const Vector<Vector2> &p_points) {
queue_redraw(); queue_redraw();
} }
void Line2D::set_closed(bool p_closed) {
_closed = p_closed;
queue_redraw();
}
bool Line2D::is_closed() const {
return _closed;
}
void Line2D::set_width(float p_width) { void Line2D::set_width(float p_width) {
if (p_width < 0.0) { if (p_width < 0.0) {
p_width = 0.0; p_width = 0.0;
@ -86,14 +102,12 @@ float Line2D::get_width() const {
} }
void Line2D::set_curve(const Ref<Curve> &p_curve) { void Line2D::set_curve(const Ref<Curve> &p_curve) {
// Cleanup previous connection if any
if (_curve.is_valid()) { if (_curve.is_valid()) {
_curve->disconnect_changed(callable_mp(this, &Line2D::_curve_changed)); _curve->disconnect_changed(callable_mp(this, &Line2D::_curve_changed));
} }
_curve = p_curve; _curve = p_curve;
// Connect to the curve so the line will update when it is changed
if (_curve.is_valid()) { if (_curve.is_valid()) {
_curve->connect_changed(callable_mp(this, &Line2D::_curve_changed)); _curve->connect_changed(callable_mp(this, &Line2D::_curve_changed));
} }
@ -156,14 +170,12 @@ Color Line2D::get_default_color() const {
} }
void Line2D::set_gradient(const Ref<Gradient> &p_gradient) { void Line2D::set_gradient(const Ref<Gradient> &p_gradient) {
// Cleanup previous connection if any
if (_gradient.is_valid()) { if (_gradient.is_valid()) {
_gradient->disconnect_changed(callable_mp(this, &Line2D::_gradient_changed)); _gradient->disconnect_changed(callable_mp(this, &Line2D::_gradient_changed));
} }
_gradient = p_gradient; _gradient = p_gradient;
// Connect to the gradient so the line will update when the Gradient is changed
if (_gradient.is_valid()) { if (_gradient.is_valid()) {
_gradient->connect_changed(callable_mp(this, &Line2D::_gradient_changed)); _gradient->connect_changed(callable_mp(this, &Line2D::_gradient_changed));
} }
@ -264,20 +276,10 @@ void Line2D::_draw() {
return; return;
} }
// TODO Is this really needed?
// Copy points for faster access
Vector<Vector2> points;
points.resize(len);
{
const Vector2 *points_read = _points.ptr();
for (int i = 0; i < len; ++i) {
points.write[i] = points_read[i];
}
}
// TODO Maybe have it as member rather than copying parameters and allocating memory? // TODO Maybe have it as member rather than copying parameters and allocating memory?
LineBuilder lb; LineBuilder lb;
lb.points = points; lb.points = _points;
lb.closed = _closed;
lb.default_color = _default_color; lb.default_color = _default_color;
lb.gradient = *_gradient; lb.gradient = *_gradient;
lb.texture_mode = _texture_mode; lb.texture_mode = _texture_mode;
@ -306,13 +308,10 @@ void Line2D::_draw() {
lb.uvs, Vector<int>(), Vector<float>(), lb.uvs, Vector<int>(), Vector<float>(),
texture_rid); texture_rid);
// DEBUG // DEBUG: Draw wireframe
// Draw wireframe // if (lb.indices.size() % 3 == 0) {
// if(lb.indices.size() % 3 == 0) { // Color col(0, 0, 0);
// Color col(0,0,0); // for (int i = 0; i < lb.indices.size(); i += 3) {
// 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 a = lb.vertices[lb.indices[i]];
// Vector2 b = lb.vertices[lb.indices[i+1]]; // Vector2 b = lb.vertices[lb.indices[i+1]];
// Vector2 c = lb.vertices[lb.indices[i+2]]; // Vector2 c = lb.vertices[lb.indices[i+2]];
@ -320,9 +319,9 @@ void Line2D::_draw() {
// draw_line(b, c, col); // draw_line(b, c, col);
// draw_line(c, a, col); // draw_line(c, a, col);
// } // }
// for(int i = 0; i < lb.vertices.size(); ++i) { // for (int i = 0; i < lb.vertices.size(); ++i) {
// Vector2 p = lb.vertices[i]; // Vector2 p = lb.vertices[i];
// draw_rect(Rect2(p.x-1, p.y-1, 2, 2), Color(0,0,0,0.5)); // draw_rect(Rect2(p.x - 1, p.y - 1, 2, 2), Color(0, 0, 0, 0.5));
// } // }
// } // }
} }
@ -350,6 +349,9 @@ void Line2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear_points"), &Line2D::clear_points); ClassDB::bind_method(D_METHOD("clear_points"), &Line2D::clear_points);
ClassDB::bind_method(D_METHOD("set_closed", "closed"), &Line2D::set_closed);
ClassDB::bind_method(D_METHOD("is_closed"), &Line2D::is_closed);
ClassDB::bind_method(D_METHOD("set_width", "width"), &Line2D::set_width); ClassDB::bind_method(D_METHOD("set_width", "width"), &Line2D::set_width);
ClassDB::bind_method(D_METHOD("get_width"), &Line2D::get_width); ClassDB::bind_method(D_METHOD("get_width"), &Line2D::get_width);
@ -387,6 +389,7 @@ void Line2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_antialiased"), &Line2D::get_antialiased); ClassDB::bind_method(D_METHOD("get_antialiased"), &Line2D::get_antialiased);
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "points"), "set_points", "get_points"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "points"), "set_points", "get_points");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "closed"), "set_closed", "is_closed");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "width", PROPERTY_HINT_NONE, "suffix:px"), "set_width", "get_width"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "width", PROPERTY_HINT_NONE, "suffix:px"), "set_width", "get_width");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "width_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve", "get_curve"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "width_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve", "get_curve");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "default_color"), "set_default_color", "get_default_color"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "default_color"), "set_default_color", "get_default_color");

View file

@ -76,6 +76,9 @@ public:
void add_point(Vector2 pos, int atpos = -1); void add_point(Vector2 pos, int atpos = -1);
void remove_point(int i); void remove_point(int i);
void set_closed(bool p_closed);
bool is_closed() const;
void set_width(float width); void set_width(float width);
float get_width() const; float get_width() const;
@ -127,6 +130,7 @@ private:
LineJointMode _joint_mode = LINE_JOINT_SHARP; LineJointMode _joint_mode = LINE_JOINT_SHARP;
LineCapMode _begin_cap_mode = LINE_CAP_NONE; LineCapMode _begin_cap_mode = LINE_CAP_NONE;
LineCapMode _end_cap_mode = LINE_CAP_NONE; LineCapMode _end_cap_mode = LINE_CAP_NONE;
bool _closed = false;
float _width = 10.0; float _width = 10.0;
Ref<Curve> _curve; Ref<Curve> _curve;
Color _default_color = Color(1, 1, 1); Color _default_color = Color(1, 1, 1);

View file

@ -30,75 +30,25 @@
#include "line_builder.h" #include "line_builder.h"
//---------------------------------------------------------------------------- #include "core/math/geometry_2d.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;
}
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);
}
// Utility method.
static inline Vector2 interpolate(const Rect2 &r, const Vector2 &v) { static inline Vector2 interpolate(const Rect2 &r, const Vector2 &v) {
return Vector2( return Vector2(
Math::lerp(r.position.x, r.position.x + r.get_size().x, v.x), Math::lerp(r.position.x, r.position.x + r.get_size().x, v.x),
Math::lerp(r.position.y, r.position.y + r.get_size().y, v.y)); Math::lerp(r.position.y, r.position.y + r.get_size().y, v.y));
} }
//----------------------------------------------------------------------------
// LineBuilder
//----------------------------------------------------------------------------
LineBuilder::LineBuilder() { LineBuilder::LineBuilder() {
} }
void LineBuilder::clear_output() {
vertices.clear();
colors.clear();
indices.clear();
uvs.clear();
}
void LineBuilder::build() { void LineBuilder::build() {
// Need at least 2 points to draw a line // Need at least 2 points to draw a line, so clear the output and return.
if (points.size() < 2) { if (points.size() < 2) {
clear_output(); vertices.clear();
colors.clear();
indices.clear();
uvs.clear();
return; return;
} }
@ -107,14 +57,21 @@ void LineBuilder::build() {
const float hw = width / 2.f; const float hw = width / 2.f;
const float hw_sq = hw * hw; const float hw_sq = hw * hw;
const float sharp_limit_sq = sharp_limit * sharp_limit; const float sharp_limit_sq = sharp_limit * sharp_limit;
const int len = points.size(); const int point_count = points.size();
const bool wrap_around = closed && point_count > 2;
_interpolate_color = gradient != nullptr;
const bool retrieve_curve = curve != nullptr;
const bool distance_required = _interpolate_color || retrieve_curve ||
texture_mode == Line2D::LINE_TEXTURE_TILE ||
texture_mode == Line2D::LINE_TEXTURE_STRETCH;
// Initial values // Initial values
Vector2 pos0 = points[0]; Vector2 pos0 = points[0];
Vector2 pos1 = points[1]; Vector2 pos1 = points[1];
Vector2 f0 = (pos1 - pos0).normalized(); Vector2 f0 = (pos1 - pos0).normalized();
Vector2 u0 = rotate90(f0); Vector2 u0 = f0.orthogonal();
Vector2 pos_up0 = pos0; Vector2 pos_up0 = pos0;
Vector2 pos_down0 = pos0; Vector2 pos_down0 = pos0;
@ -124,32 +81,37 @@ void LineBuilder::build() {
float current_distance0 = 0.f; float current_distance0 = 0.f;
float current_distance1 = 0.f; float current_distance1 = 0.f;
float total_distance = 0.f; float total_distance = 0.f;
float width_factor = 1.f; float width_factor = 1.f;
_interpolate_color = gradient != nullptr; float modified_hw = hw;
bool retrieve_curve = curve != nullptr; if (retrieve_curve) {
bool distance_required = _interpolate_color || width_factor = curve->sample_baked(0.f);
retrieve_curve || modified_hw = hw * width_factor;
texture_mode == Line2D::LINE_TEXTURE_TILE || }
texture_mode == Line2D::LINE_TEXTURE_STRETCH;
if (distance_required) { if (distance_required) {
total_distance = calculate_total_distance(points); // Calculate the total distance.
//Adjust totalDistance. for (int i = 1; i < point_count; ++i) {
// The line's outer length will be a little higher due to begin and end caps total_distance += points[i].distance_to(points[i - 1]);
if (begin_cap_mode == Line2D::LINE_CAP_BOX || begin_cap_mode == Line2D::LINE_CAP_ROUND) {
if (retrieve_curve) {
total_distance += width * curve->sample_baked(0.f) * 0.5f;
} else {
total_distance += width * 0.5f;
}
} }
if (end_cap_mode == Line2D::LINE_CAP_BOX || end_cap_mode == Line2D::LINE_CAP_ROUND) { if (wrap_around) {
if (retrieve_curve) { total_distance += points[point_count - 1].distance_to(pos0);
total_distance += width * curve->sample_baked(1.f) * 0.5f; } else {
} else { // Adjust the total distance.
total_distance += width * 0.5f; // The line's outer length may be a little higher due to the end caps.
if (begin_cap_mode == Line2D::LINE_CAP_BOX || begin_cap_mode == Line2D::LINE_CAP_ROUND) {
total_distance += modified_hw;
}
if (end_cap_mode == Line2D::LINE_CAP_BOX || end_cap_mode == Line2D::LINE_CAP_ROUND) {
if (retrieve_curve) {
total_distance += hw * curve->sample_baked(1.f);
} else {
total_distance += hw;
}
} }
} }
} }
if (_interpolate_color) { if (_interpolate_color) {
color0 = gradient->get_color(0); color0 = gradient->get_color(0);
} else { } else {
@ -159,34 +121,31 @@ void LineBuilder::build() {
float uvx0 = 0.f; float uvx0 = 0.f;
float uvx1 = 0.f; float uvx1 = 0.f;
if (retrieve_curve) { pos_up0 += u0 * modified_hw;
width_factor = curve->sample_baked(0.f); pos_down0 -= u0 * modified_hw;
}
pos_up0 += u0 * hw * width_factor;
pos_down0 -= u0 * hw * width_factor;
// Begin cap // Begin cap
if (begin_cap_mode == Line2D::LINE_CAP_BOX) { if (!wrap_around) {
// Push back first vertices a little bit if (begin_cap_mode == Line2D::LINE_CAP_BOX) {
pos_up0 -= f0 * hw * width_factor; // Push back first vertices a little bit.
pos_down0 -= f0 * hw * width_factor; pos_up0 -= f0 * modified_hw;
pos_down0 -= f0 * modified_hw;
current_distance0 += hw * width_factor; current_distance0 += modified_hw;
current_distance1 = current_distance0; current_distance1 = current_distance0;
} else if (begin_cap_mode == Line2D::LINE_CAP_ROUND) { } else if (begin_cap_mode == Line2D::LINE_CAP_ROUND) {
if (texture_mode == Line2D::LINE_TEXTURE_TILE) { if (texture_mode == Line2D::LINE_TEXTURE_TILE) {
uvx0 = width_factor * 0.5f / tile_aspect; uvx0 = width_factor * 0.5f / tile_aspect;
} else if (texture_mode == Line2D::LINE_TEXTURE_STRETCH) { } else if (texture_mode == Line2D::LINE_TEXTURE_STRETCH) {
uvx0 = width * width_factor / total_distance; uvx0 = width * width_factor / total_distance;
}
new_arc(pos0, pos_up0 - pos0, -Math_PI, color0, Rect2(0.f, 0.f, uvx0 * 2, 1.f));
current_distance0 += modified_hw;
current_distance1 = current_distance0;
} }
new_arc(pos0, pos_up0 - pos0, -Math_PI, color0, Rect2(0.f, 0.f, uvx0 * 2, 1.f)); strip_begin(pos_up0, pos_down0, color0, uvx0);
current_distance0 += hw * width_factor;
current_distance1 = current_distance0;
} }
strip_begin(pos_up0, pos_down0, color0, uvx0);
/* /*
* pos_up0 ------------- pos_up1 -------------------- * pos_up0 ------------- pos_up1 --------------------
* | | * | |
@ -200,19 +159,30 @@ void LineBuilder::build() {
// http://labs.hyperandroid.com/tag/opengl-lines // http://labs.hyperandroid.com/tag/opengl-lines
// (not the same implementation but visuals help a lot) // (not the same implementation but visuals help a lot)
// If the polyline wraps around, then draw two more segments with joints:
// The last one, which should normally end with an end cap, and the one that matches the end and the beginning.
int segments_count = wrap_around ? point_count : (point_count - 2);
// The wraparound case starts with a "fake walk" from the end of the polyline
// to its beginning, so that its first joint is correct, without drawing anything.
int first_point = wrap_around ? -1 : 1;
// If the line wraps around, these variables will be used for the final segment.
Vector2 first_pos_up, first_pos_down;
bool is_first_joint_sharp = false;
// For each additional segment // For each additional segment
for (int i = 1; i < len - 1; ++i) { for (int i = first_point; i <= segments_count; ++i) {
pos1 = points[i]; pos1 = points[(i == -1) ? point_count - 1 : i % point_count]; // First point.
Vector2 pos2 = points[i + 1]; Vector2 pos2 = points[(i + 1) % point_count]; // Second point.
Vector2 f1 = (pos2 - pos1).normalized(); Vector2 f1 = (pos2 - pos1).normalized();
Vector2 u1 = rotate90(f1); Vector2 u1 = f1.orthogonal();
// Determine joint orientation // Determine joint orientation.
const float dp = u0.dot(f1); float dp = u0.dot(f1);
const Orientation orientation = (dp > 0.f ? UP : DOWN); const Orientation orientation = (dp > 0.f ? UP : DOWN);
if (distance_required) { if (distance_required && i >= 1) {
current_distance1 += pos0.distance_to(pos1); current_distance1 += pos0.distance_to(pos1);
} }
if (_interpolate_color) { if (_interpolate_color) {
@ -220,15 +190,14 @@ void LineBuilder::build() {
} }
if (retrieve_curve) { if (retrieve_curve) {
width_factor = curve->sample_baked(current_distance1 / total_distance); width_factor = curve->sample_baked(current_distance1 / total_distance);
modified_hw = hw * width_factor;
} }
Vector2 inner_normal0, inner_normal1; Vector2 inner_normal0 = u0 * modified_hw;
if (orientation == UP) { Vector2 inner_normal1 = u1 * modified_hw;
inner_normal0 = u0 * hw * width_factor; if (orientation == DOWN) {
inner_normal1 = u1 * hw * width_factor; inner_normal0 = -inner_normal0;
} else { inner_normal1 = -inner_normal1;
inner_normal0 = -u0 * hw * width_factor;
inner_normal1 = -u1 * hw * width_factor;
} }
/* /*
@ -245,18 +214,18 @@ void LineBuilder::build() {
* / * /
*/ */
// Find inner intersection at the joint // Find inner intersection at the joint.
Vector2 corner_pos_in, corner_pos_out; Vector2 corner_pos_in, corner_pos_out;
SegmentIntersectionResult intersection_result = segment_intersection( bool is_intersecting = Geometry2D::segment_intersects_segment(
pos0 + inner_normal0, pos1 + inner_normal0, pos0 + inner_normal0, pos1 + inner_normal0,
pos1 + inner_normal1, pos2 + inner_normal1, pos1 + inner_normal1, pos2 + inner_normal1,
&corner_pos_in); &corner_pos_in);
if (intersection_result == SEGMENT_INTERSECT) { if (is_intersecting) {
// Inner parts of the segments intersect // Inner parts of the segments intersect.
corner_pos_out = 2.f * pos1 - corner_pos_in; corner_pos_out = 2.f * pos1 - corner_pos_in;
} else { } else {
// No intersection, segments are either parallel or too sharp // No intersection, segments are too sharp or they overlap.
corner_pos_in = pos1 + inner_normal0; corner_pos_in = pos1 + inner_normal0;
corner_pos_out = pos1 - inner_normal0; corner_pos_out = pos1 - inner_normal0;
} }
@ -273,8 +242,8 @@ void LineBuilder::build() {
Line2D::LineJointMode current_joint_mode = joint_mode; Line2D::LineJointMode current_joint_mode = joint_mode;
Vector2 pos_up1, pos_down1; Vector2 pos_up1, pos_down1;
if (intersection_result == SEGMENT_INTERSECT) { if (is_intersecting) {
// Fallback on bevel if sharp angle is too high (because it would produce very long miters) // Fallback on bevel if sharp angle is too high (because it would produce very long miters).
float width_factor_sq = width_factor * width_factor; float width_factor_sq = width_factor * width_factor;
if (current_joint_mode == Line2D::LINE_JOINT_SHARP && corner_pos_out.distance_squared_to(pos1) / (hw_sq * width_factor_sq) > sharp_limit_sq) { if (current_joint_mode == Line2D::LINE_JOINT_SHARP && corner_pos_out.distance_squared_to(pos1) / (hw_sq * width_factor_sq) > sharp_limit_sq) {
current_joint_mode = Line2D::LINE_JOINT_BEVEL; current_joint_mode = Line2D::LINE_JOINT_BEVEL;
@ -288,57 +257,78 @@ void LineBuilder::build() {
// Bevel or round // Bevel or round
if (orientation == UP) { if (orientation == UP) {
pos_up1 = corner_pos_up; pos_up1 = corner_pos_up;
pos_down1 = pos1 - u0 * hw * width_factor; pos_down1 = pos1 - u0 * modified_hw;
} else { } else {
pos_up1 = pos1 + u0 * hw * width_factor; pos_up1 = pos1 + u0 * modified_hw;
pos_down1 = corner_pos_down; pos_down1 = corner_pos_down;
} }
} }
} else { } else {
// No intersection: fallback // No intersection: fallback
if (current_joint_mode == Line2D::LINE_JOINT_SHARP) { if (current_joint_mode == Line2D::LINE_JOINT_SHARP) {
// There is no fallback implementation for LINE_JOINT_SHARP so switch to the LINE_JOINT_BEVEL // There is no fallback implementation for LINE_JOINT_SHARP so switch to the LINE_JOINT_BEVEL.
current_joint_mode = Line2D::LINE_JOINT_BEVEL; current_joint_mode = Line2D::LINE_JOINT_BEVEL;
} }
pos_up1 = corner_pos_up; pos_up1 = corner_pos_up;
pos_down1 = corner_pos_down; pos_down1 = corner_pos_down;
} }
// Add current line body quad // Triangles are clockwise.
// Triangles are clockwise
if (texture_mode == Line2D::LINE_TEXTURE_TILE) { if (texture_mode == Line2D::LINE_TEXTURE_TILE) {
uvx1 = current_distance1 / (width * tile_aspect); uvx1 = current_distance1 / (width * tile_aspect);
} else if (texture_mode == Line2D::LINE_TEXTURE_STRETCH) { } else if (texture_mode == Line2D::LINE_TEXTURE_STRETCH) {
uvx1 = current_distance1 / total_distance; uvx1 = current_distance1 / total_distance;
} }
strip_add_quad(pos_up1, pos_down1, color1, uvx1); // Swap vars for use in the next line.
// Swap vars for use in the next line
color0 = color1; color0 = color1;
u0 = u1; u0 = u1;
f0 = f1; f0 = f1;
pos0 = pos1; pos0 = pos1;
if (intersection_result == SEGMENT_INTERSECT) { if (is_intersecting) {
if (current_joint_mode == Line2D::LINE_JOINT_SHARP) { if (current_joint_mode == Line2D::LINE_JOINT_SHARP) {
pos_up0 = pos_up1; pos_up0 = pos_up1;
pos_down0 = pos_down1; pos_down0 = pos_down1;
} else { } else {
if (orientation == UP) { if (orientation == UP) {
pos_up0 = corner_pos_up; pos_up0 = corner_pos_up;
pos_down0 = pos1 - u1 * hw * width_factor; pos_down0 = pos1 - u1 * modified_hw;
} else { } else {
pos_up0 = pos1 + u1 * hw * width_factor; pos_up0 = pos1 + u1 * modified_hw;
pos_down0 = corner_pos_down; pos_down0 = corner_pos_down;
} }
} }
} else { } else {
pos_up0 = pos1 + u1 * hw * width_factor; pos_up0 = pos1 + u1 * modified_hw;
pos_down0 = pos1 - u1 * hw * width_factor; pos_down0 = pos1 - u1 * modified_hw;
} }
// From this point, bu0 and bd0 concern the next segment
// Add joint geometry // End the "fake pass" in the closed line case before the drawing subroutine.
if (i == -1) {
continue;
}
// For wrap-around polylines, store some kind of start positions of the first joint for the final connection.
if (wrap_around && i == 0) {
Vector2 first_pos_center = (pos_up1 + pos_down1) / 2;
float lerp_factor = 1.0 / width_factor;
first_pos_up = first_pos_center.lerp(pos_up1, lerp_factor);
first_pos_down = first_pos_center.lerp(pos_down1, lerp_factor);
is_first_joint_sharp = current_joint_mode == Line2D::LINE_JOINT_SHARP;
}
// Add current line body quad.
if (wrap_around && retrieve_curve && !is_first_joint_sharp && i == segments_count) {
// If the width curve is not seamless, we might need to fetch the line's start points to use them for the final connection.
Vector2 first_pos_center = (first_pos_up + first_pos_down) / 2;
strip_add_quad(first_pos_center.lerp(first_pos_up, width_factor), first_pos_center.lerp(first_pos_down, width_factor), color1, uvx1);
return;
} else {
strip_add_quad(pos_up1, pos_down1, color1, uvx1);
}
// From this point, bu0 and bd0 concern the next segment.
// Add joint geometry.
if (current_joint_mode != Line2D::LINE_JOINT_SHARP) { if (current_joint_mode != Line2D::LINE_JOINT_SHARP) {
/* ________________ cbegin /* ________________ cbegin
* / \ * / \
@ -358,64 +348,68 @@ void LineBuilder::build() {
cend = pos_up0; cend = pos_up0;
} }
if (current_joint_mode == Line2D::LINE_JOINT_BEVEL) { if (current_joint_mode == Line2D::LINE_JOINT_BEVEL && !(wrap_around && i == segments_count)) {
strip_add_tri(cend, orientation); strip_add_tri(cend, orientation);
} else if (current_joint_mode == Line2D::LINE_JOINT_ROUND) { } else if (current_joint_mode == Line2D::LINE_JOINT_ROUND && !(wrap_around && i == segments_count)) {
Vector2 vbegin = cbegin - pos1; Vector2 vbegin = cbegin - pos1;
Vector2 vend = cend - pos1; Vector2 vend = cend - pos1;
strip_add_arc(pos1, vbegin.angle_to(vend), orientation); strip_add_arc(pos1, vbegin.angle_to(vend), orientation);
} }
if (intersection_result != SEGMENT_INTERSECT) { if (!is_intersecting) {
// In this case the joint is too corrupted to be re-used, // In this case the joint is too corrupted to be re-used,
// start again the strip with fallback points // start again the strip with fallback points
strip_begin(pos_up0, pos_down0, color1, uvx1); strip_begin(pos_up0, pos_down0, color1, uvx1);
} }
} }
} }
// Last (or only) segment
pos1 = points[points.size() - 1];
if (distance_required) { // Draw the last (or only) segment, with its end cap logic.
current_distance1 += pos0.distance_to(pos1); if (!wrap_around) {
} pos1 = points[point_count - 1];
if (_interpolate_color) {
color1 = gradient->get_color(gradient->get_point_count() - 1);
}
if (retrieve_curve) {
width_factor = curve->sample_baked(1.f);
}
Vector2 pos_up1 = pos1 + u0 * hw * width_factor; if (distance_required) {
Vector2 pos_down1 = pos1 - u0 * hw * width_factor; current_distance1 += pos0.distance_to(pos1);
}
// End cap (box) if (_interpolate_color) {
if (end_cap_mode == Line2D::LINE_CAP_BOX) { color1 = gradient->get_color(gradient->get_point_count() - 1);
pos_up1 += f0 * hw * width_factor; }
pos_down1 += f0 * hw * width_factor; if (retrieve_curve) {
width_factor = curve->sample_baked(1.f);
current_distance1 += hw * width_factor; modified_hw = hw * width_factor;
} }
if (texture_mode == Line2D::LINE_TEXTURE_TILE) { Vector2 pos_up1 = pos1 + u0 * modified_hw;
uvx1 = current_distance1 / (width * tile_aspect); Vector2 pos_down1 = pos1 - u0 * modified_hw;
} else if (texture_mode == Line2D::LINE_TEXTURE_STRETCH) {
uvx1 = current_distance1 / total_distance; // Add extra distance for a box end cap.
} if (end_cap_mode == Line2D::LINE_CAP_BOX) {
pos_up1 += f0 * modified_hw;
strip_add_quad(pos_up1, pos_down1, color1, uvx1); pos_down1 += f0 * modified_hw;
// End cap (round) current_distance1 += modified_hw;
if (end_cap_mode == Line2D::LINE_CAP_ROUND) { }
// Note: color is not used in case we don't interpolate...
Color color = _interpolate_color ? gradient->get_color(gradient->get_point_count() - 1) : Color(0, 0, 0); if (texture_mode == Line2D::LINE_TEXTURE_TILE) {
float dist = 0; uvx1 = current_distance1 / (width * tile_aspect);
if (texture_mode == Line2D::LINE_TEXTURE_TILE) { } else if (texture_mode == Line2D::LINE_TEXTURE_STRETCH) {
dist = width_factor / tile_aspect; uvx1 = current_distance1 / total_distance;
} else if (texture_mode == Line2D::LINE_TEXTURE_STRETCH) { }
dist = width * width_factor / total_distance;
strip_add_quad(pos_up1, pos_down1, color1, uvx1);
// Custom drawing for a round end cap.
if (end_cap_mode == Line2D::LINE_CAP_ROUND) {
// Note: color is not used in case we don't interpolate.
Color color = _interpolate_color ? gradient->get_color(gradient->get_point_count() - 1) : Color(0, 0, 0);
float dist = 0;
if (texture_mode == Line2D::LINE_TEXTURE_TILE) {
dist = width_factor / tile_aspect;
} else if (texture_mode == Line2D::LINE_TEXTURE_STRETCH) {
dist = width * width_factor / total_distance;
}
new_arc(pos1, pos_up1 - pos1, Math_PI, color, Rect2(uvx1 - 0.5f * dist, 0.f, dist, 1.f));
} }
new_arc(pos1, pos_up1 - pos1, Math_PI, color, Rect2(uvx1 - 0.5f * dist, 0.f, dist, 1.f));
} }
} }

View file

@ -41,6 +41,7 @@ public:
Line2D::LineJointMode joint_mode = Line2D::LINE_JOINT_SHARP; Line2D::LineJointMode joint_mode = Line2D::LINE_JOINT_SHARP;
Line2D::LineCapMode begin_cap_mode = Line2D::LINE_CAP_NONE; Line2D::LineCapMode begin_cap_mode = Line2D::LINE_CAP_NONE;
Line2D::LineCapMode end_cap_mode = Line2D::LINE_CAP_NONE; Line2D::LineCapMode end_cap_mode = Line2D::LINE_CAP_NONE;
bool closed = false;
float width = 10.0; float width = 10.0;
Curve *curve = nullptr; Curve *curve = nullptr;
Color default_color = Color(0.4, 0.5, 1); Color default_color = Color(0.4, 0.5, 1);
@ -61,7 +62,6 @@ public:
LineBuilder(); LineBuilder();
void build(); void build();
void clear_output();
private: private:
enum Orientation { enum Orientation {