Skip to content

Commit

Permalink
Implement segmenting of Line2D for better fidelity when using width c…
Browse files Browse the repository at this point in the history
…urves
  • Loading branch information
chryan committed Aug 14, 2024
1 parent 06fbc83 commit fa79d4f
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 13 deletions.
3 changes: 3 additions & 0 deletions doc/classes/Line2D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@
[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 name="curve_offset" type="float" setter="set_curve_offset" getter="get_curve_offset" default="0.0">
The horizontal offset value used when sampling from the width curve. Typically ranges from -1.0 to 1.0.
</member>
<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.
</member>
Expand Down
66 changes: 64 additions & 2 deletions scene/2d/line_2d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ Ref<Curve> Line2D::get_curve() const {
return _curve;
}

void Line2D::set_curve_offset(float curve_offset) {
_curve_offset = curve_offset;
queue_redraw();
}

Vector<Vector2> Line2D::get_points() const {
return _points;
}
Expand Down Expand Up @@ -277,9 +282,61 @@ void Line2D::_draw() {
}

// TODO Maybe have it as member rather than copying parameters and allocating memory?

int num_segments = _curve.is_valid() ? _curve->get_bake_resolution() : -1;
if (static_cast<int>(_generated_draw_points.get_capacity()) < num_segments)
_generated_draw_points.reserve(num_segments);
_generated_draw_points.clear();

bool just_use_points = num_segments == -1;
// We have less segments than what's defined, so we just use the points.
if (just_use_points) {
_generated_draw_points = _points;
} else {
// Set the first and last points from our line data.
_generated_draw_points.push_back(_points[0]);

// TODO: Cache the memory somewhere so we don't keep allocating memory
int line_count = _closed ? len : len - 1;
LocalVector<float> dists_between_points;
LocalVector<Vector2> line_directions;
dists_between_points.reserve(line_count);
line_directions.reserve(line_count);

// Gather the total line length, with distances and directions for each sub-line
float total_line_length = 0.0f;
for (int i = 0; i < line_count; ++i) {
Vector2 line_dir = _points[(i + 1) % len] - _points[i];
float distance_bt_points = line_dir.length();
total_line_length += distance_bt_points;
dists_between_points.push_back(distance_bt_points);
line_directions.push_back(line_dir / distance_bt_points);
}

// Can't have negative segments!
float segment_length = total_line_length / MAX(1, static_cast<float>(num_segments));
for (int s = 0; s < line_count; ++s) {
int segments_for_line = static_cast<int>(Math::ceil(dists_between_points[s] / segment_length));

Vector2 line_start = _points[s];
Vector2 line_end = _points[(s + 1) % len];
float line_distance = dists_between_points[s];
_generated_draw_points.push_back(_points[s]);

if (segments_for_line > 1) {
for (int l = 1; l < segments_for_line; ++l) {
float current_length_from_line_start = segment_length * static_cast<float>(l);
_generated_draw_points.push_back(line_start + (line_end - line_start) * (current_length_from_line_start / line_distance));
}
}
}
// Set last point.
_generated_draw_points.push_back(_points[line_count % len]);
}

LineBuilder lb;
lb.points = _points;
lb.closed = _closed;
lb.closed = _closed && just_use_points;
lb.points = &_generated_draw_points;
lb.default_color = _default_color;
lb.gradient = *_gradient;
lb.texture_mode = _texture_mode;
Expand All @@ -290,6 +347,7 @@ void Line2D::_draw() {
lb.sharp_limit = _sharp_limit;
lb.width = _width;
lb.curve = *_curve;
lb.curve_offset = _curve_offset;

RID texture_rid;
if (_texture.is_valid()) {
Expand Down Expand Up @@ -358,6 +416,9 @@ void Line2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_curve", "curve"), &Line2D::set_curve);
ClassDB::bind_method(D_METHOD("get_curve"), &Line2D::get_curve);

ClassDB::bind_method(D_METHOD("set_curve_offset", "curve_offset"), &Line2D::set_curve_offset);
ClassDB::bind_method(D_METHOD("get_curve_offset"), &Line2D::get_curve_offset);

ClassDB::bind_method(D_METHOD("set_default_color", "color"), &Line2D::set_default_color);
ClassDB::bind_method(D_METHOD("get_default_color"), &Line2D::get_default_color);

Expand Down Expand Up @@ -392,6 +453,7 @@ void Line2D::_bind_methods() {
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::OBJECT, "width_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve", "get_curve");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "curve_offset", PROPERTY_HINT_RANGE, "-1.0,1.0,0.01"), "set_curve_offset", "get_curve_offset");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "default_color"), "set_default_color", "get_default_color");
ADD_GROUP("Fill", "");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gradient", PROPERTY_HINT_RESOURCE_TYPE, "Gradient"), "set_gradient", "get_gradient");
Expand Down
5 changes: 5 additions & 0 deletions scene/2d/line_2d.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ class Line2D : public Node2D {
void set_curve(const Ref<Curve> &curve);
Ref<Curve> get_curve() const;

void set_curve_offset(float curve_offset);
float get_curve_offset() const { return _curve_offset; }

void set_default_color(Color color);
Color get_default_color() const;

Expand Down Expand Up @@ -127,12 +130,14 @@ class Line2D : public Node2D {

private:
Vector<Vector2> _points;
LocalVector<Vector2> _generated_draw_points;
LineJointMode _joint_mode = LINE_JOINT_SHARP;
LineCapMode _begin_cap_mode = LINE_CAP_NONE;
LineCapMode _end_cap_mode = LINE_CAP_NONE;
bool _closed = false;
float _width = 10.0;
Ref<Curve> _curve;
float _curve_offset = 0.0f;
Color _default_color = Color(1, 1, 1);
Ref<Gradient> _gradient;
Ref<Texture2D> _texture;
Expand Down
21 changes: 11 additions & 10 deletions scene/2d/line_builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ LineBuilder::LineBuilder() {

void LineBuilder::build() {
// Need at least 2 points to draw a line, so clear the output and return.
if (points.size() < 2) {
if (points->size() < 2) {
vertices.clear();
colors.clear();
indices.clear();
Expand All @@ -57,7 +57,7 @@ void LineBuilder::build() {
const float hw = width / 2.f;
const float hw_sq = hw * hw;
const float sharp_limit_sq = sharp_limit * sharp_limit;
const int point_count = points.size();
const int point_count = static_cast<int>(points->size());
const bool wrap_around = closed && point_count > 2;

_interpolate_color = gradient != nullptr;
Expand All @@ -68,8 +68,8 @@ void LineBuilder::build() {

// Initial values

Vector2 pos0 = points[0];
Vector2 pos1 = points[1];
Vector2 pos0 = (*points)[0];
Vector2 pos1 = (*points)[1];
Vector2 f0 = (pos1 - pos0).normalized();
Vector2 u0 = f0.orthogonal();
Vector2 pos_up0 = pos0;
Expand All @@ -92,10 +92,10 @@ void LineBuilder::build() {
if (distance_required) {
// Calculate the total distance.
for (int i = 1; i < point_count; ++i) {
total_distance += points[i].distance_to(points[i - 1]);
total_distance += (*points)[i].distance_to((*points)[i - 1]);
}
if (wrap_around) {
total_distance += points[point_count - 1].distance_to(pos0);
total_distance += (*points)[point_count - 1].distance_to(pos0);
} else {
// Adjust the total distance.
// The line's outer length may be a little higher due to the end caps.
Expand Down Expand Up @@ -172,8 +172,8 @@ void LineBuilder::build() {

// For each additional segment
for (int i = first_point; i <= segments_count; ++i) {
pos1 = points[(i == -1) ? point_count - 1 : i % point_count]; // First point.
Vector2 pos2 = points[(i + 1) % point_count]; // Second point.
pos1 = (*points)[(i == -1) ? point_count - 1 : i % point_count]; // First point.
Vector2 pos2 = (*points)[(i + 1) % point_count]; // Second point.

Vector2 f1 = (pos2 - pos1).normalized();
Vector2 u1 = f1.orthogonal();
Expand All @@ -189,7 +189,8 @@ void LineBuilder::build() {
color1 = gradient->get_color_at_offset(current_distance1 / total_distance);
}
if (retrieve_curve) {
width_factor = curve->sample_baked(current_distance1 / total_distance);
float offset = CLAMP((current_distance1 / total_distance) - curve_offset, 0.0f, 1.0f);
width_factor = curve->sample_baked(offset);
modified_hw = hw * width_factor;
}

Expand Down Expand Up @@ -379,7 +380,7 @@ void LineBuilder::build() {

// Draw the last (or only) segment, with its end cap logic.
if (!wrap_around) {
pos1 = points[point_count - 1];
pos1 = (*points)[point_count - 1];

if (distance_required) {
current_distance1 += pos0.distance_to(pos1);
Expand Down
3 changes: 2 additions & 1 deletion scene/2d/line_builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@ class LineBuilder {
public:
// TODO Move in a struct and reference it
// Input
Vector<Vector2> points;
LocalVector<Vector2> *points;
Line2D::LineJointMode joint_mode = Line2D::LINE_JOINT_SHARP;
Line2D::LineCapMode begin_cap_mode = Line2D::LINE_CAP_NONE;
Line2D::LineCapMode end_cap_mode = Line2D::LINE_CAP_NONE;
bool closed = false;
float width = 10.0;
Curve *curve = nullptr;
float curve_offset = 0.0f;
Color default_color = Color(0.4, 0.5, 1);
Gradient *gradient = nullptr;
Line2D::LineTextureMode texture_mode = Line2D::LineTextureMode::LINE_TEXTURE_NONE;
Expand Down

0 comments on commit fa79d4f

Please sign in to comment.