Skip to content

Commit

Permalink
Merge branch 'line2d-segments' of https://github.com/chryan/godot
Browse files Browse the repository at this point in the history
  • Loading branch information
chryan committed Sep 6, 2024
2 parents 66af2c6 + 636c352 commit 588af53
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 8 deletions.
7 changes: 7 additions & 0 deletions doc/classes/Line2D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@
[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="min_curve_line_segments" type="int" setter="set_min_curve_line_segments" getter="get_min_curve_line_segments" default="1">
Subdivide the line into the number of at least [member min_curve_line_segments] segments. The resulting segment count varies depending on the use of a fill gradient and the distances between points.
[b]Note:[/b] Using higher values results in more segments (and vertices/indices) that need to be generated which can degrade performance, particularly if you are animating properties on your line.
</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
129 changes: 127 additions & 2 deletions scene/2d/line_2d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,16 @@ Ref<Curve> Line2D::get_curve() const {
return _curve;
}

void Line2D::set_min_curve_line_segments(int min_curve_line_segments) {
_min_curve_line_segments = min_curve_line_segments;
queue_redraw();
}

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 @@ -276,9 +286,115 @@ void Line2D::_draw() {
return;
}

// TODO Maybe have it as member rather than copying parameters and allocating memory?
// NOTE:
// Sublines refer to the lines between points before curve segmentation.

LineBuilder lb;
lb.points = _points;
LocalVector<Vector2> &generated_draw_points = lb.points;
LocalVector<Vector2> gradient_inclusive_points;
// We insert gradient points that correspond to a position on our normalized line (e.g. gradient point of 0.5 means we add a point in the at the half-way point of our total line).
if (_gradient.is_valid()) {
int gradient_point_count = _gradient->get_point_count();

int subline_count = len - 1;
LocalVector<float> subline_lengths;
LocalVector<Vector2> sublines;
subline_lengths.reserve(subline_count);
sublines.reserve(len + _gradient->get_point_count() - 1);

// Gather the total line length, and distances and directions for each subline
float total_line_length = 0.0f;
for (int i = 0; i < subline_count; ++i) {
Vector2 subline_dir = _points[i + 1] - _points[i];
float subline_length = subline_dir.length();
total_line_length += subline_length;
subline_lengths.push_back(subline_length);
sublines.push_back(subline_dir);
}

// Reserve the right amount of memory so we're not constantly reallocating as we're pushing items in the back.
gradient_inclusive_points.reserve(static_cast<unsigned int>(_points.size()) + static_cast<unsigned int>(gradient_point_count));

int gradient_idx = 0;

float cumulative_line_length = 0.0f;
for (int line_idx = 0; line_idx < subline_count; ++line_idx) {
// All current calculations are based on the current subline.
int wrapped_line_idx = line_idx % static_cast<int>(_points.size());
const Vector2 &curr_subline_start = _points[wrapped_line_idx];
const Vector2 &curr_subline = sublines[wrapped_line_idx];
const float &curr_subline_length = subline_lengths[wrapped_line_idx];

// This is where our current subline points lay on our normalized line scale.
float curr_subline_start_offset = cumulative_line_length / total_line_length;
float curr_subline_end_offset = (cumulative_line_length + curr_subline_length) / total_line_length;

gradient_inclusive_points.push_back(curr_subline_start);
// We check to see if there's a gradient point that has an offset between our current subline.
// For the curve offset, we only care about the X axis that ranges from 0 to 1, corresponding with our line normalization.
while (gradient_idx < _gradient->get_point_count()) {
float curr_gradient_point_offset = _gradient->get_offset(gradient_idx);
float relative_gradient_point_offset = (curr_gradient_point_offset - curr_subline_start_offset) / (curr_subline_end_offset - curr_subline_start_offset);
// Only add a point if the point offset is between our subline start and end.
if (relative_gradient_point_offset >= 1.0f) {
break;
}
if (relative_gradient_point_offset > 0.0f) {
gradient_inclusive_points.push_back(curr_subline_start + curr_subline * relative_gradient_point_offset);
}
++gradient_idx;
}
cumulative_line_length += curr_subline_length;
}
gradient_inclusive_points.push_back(_points[subline_count]);
} else {
gradient_inclusive_points = _points;
}

int subline_count = static_cast<int>(gradient_inclusive_points.size()) - 1;
int num_segments = _curve.is_valid() ? _min_curve_line_segments : -1;
num_segments = MAX(subline_count, num_segments);
generated_draw_points.reserve(num_segments);
generated_draw_points.clear();

// We have less segments than what's defined, so we just use the points.
if (num_segments <= subline_count) {
generated_draw_points = gradient_inclusive_points;
} else {
len = gradient_inclusive_points.size();

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

// Gather the total line length, and distances and directions for each segment
float total_line_length = 0.0f;
for (int line_idx = 0; line_idx < line_count; ++line_idx) {
Vector2 line_dir = gradient_inclusive_points[(line_idx + 1) % len] - gradient_inclusive_points[line_idx];
float subline_length = line_dir.length();
total_line_length += subline_length;
subline_lengths.push_back(subline_length);
}

float segment_length = total_line_length / MAX(1, static_cast<float>(num_segments));
for (int line_idx = 0; line_idx < line_count; ++line_idx) {
float subline_length = subline_lengths[line_idx];
int subline_segments_count = static_cast<int>(Math::ceil(subline_length / segment_length));
Vector2 subline_start = gradient_inclusive_points[line_idx];
Vector2 subline_end = gradient_inclusive_points[(line_idx + 1) % len];
generated_draw_points.push_back(gradient_inclusive_points[line_idx]);
for (int l = 1; l < subline_segments_count; ++l) {
float current_segment_relative_start = segment_length * static_cast<float>(l);
generated_draw_points.push_back(subline_start + (subline_end - subline_start) * (current_segment_relative_start / subline_length));
}
}

// We don't add the last point so that the line builder can properly cap the ends
if (!_closed)
generated_draw_points.push_back(gradient_inclusive_points[line_count % len]);
}

lb.closed = _closed;
lb.default_color = _default_color;
lb.gradient = *_gradient;
Expand All @@ -290,6 +406,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 +475,12 @@ 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_min_curve_line_segments", "curve_offset"), &Line2D::set_min_curve_line_segments);
ClassDB::bind_method(D_METHOD("get_min_curve_line_segments"), &Line2D::get_min_curve_line_segments);

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 +515,8 @@ 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::INT, "min_curve_line_segments", PROPERTY_HINT_RANGE, "1,64,1,or_greater"), "set_min_curve_line_segments", "get_min_curve_line_segments");
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
8 changes: 8 additions & 0 deletions scene/2d/line_2d.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ class Line2D : public Node2D {
void set_curve(const Ref<Curve> &curve);
Ref<Curve> get_curve() const;

void set_min_curve_line_segments(int min_curve_line_segments);
int get_min_curve_line_segments() const { return _min_curve_line_segments; }

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 @@ -133,6 +139,8 @@ class Line2D : public Node2D {
bool _closed = false;
float _width = 10.0;
Ref<Curve> _curve;
int _min_curve_line_segments = 1;
float _curve_offset = 0.0f;
Color _default_color = Color(1, 1, 1);
Ref<Gradient> _gradient;
Ref<Texture2D> _texture;
Expand Down
11 changes: 6 additions & 5 deletions scene/2d/line_builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 Down Expand Up @@ -113,7 +113,7 @@ void LineBuilder::build() {
}

if (_interpolate_color) {
color0 = gradient->get_color(0);
color0 = gradient->get_color_at_offset(0.0f);
} else {
colors.push_back(default_color);
}
Expand Down Expand Up @@ -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 @@ -385,7 +386,7 @@ void LineBuilder::build() {
current_distance1 += pos0.distance_to(pos1);
}
if (_interpolate_color) {
color1 = gradient->get_color(gradient->get_point_count() - 1);
color1 = gradient->get_color_at_offset(1.0f);
}
if (retrieve_curve) {
width_factor = curve->sample_baked(1.f);
Expand Down Expand Up @@ -414,7 +415,7 @@ void LineBuilder::build() {
// 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);
Color color = _interpolate_color ? gradient->get_color_at_offset(1.0f) : Color(0, 0, 0);
float dist = 0;
if (texture_mode == Line2D::LINE_TEXTURE_TILE) {
dist = width_factor / tile_aspect;
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
1 change: 1 addition & 0 deletions scene/resources/curve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ void Curve::set_bake_resolution(int p_resolution) {
ERR_FAIL_COND(p_resolution > 1000);
_bake_resolution = p_resolution;
_baked_cache_dirty = true;
emit_changed();
}

real_t Curve::sample_baked(real_t p_offset) const {
Expand Down

0 comments on commit 588af53

Please sign in to comment.