Skip to content

Commit

Permalink
Add property for constraining the distance between endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
mphe committed Jul 15, 2024
1 parent 1c56376 commit 9195e42
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 19 deletions.
7 changes: 7 additions & 0 deletions demo/addons/ropesim/Rope.gd
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ signal on_point_count_changed()
## Overall rope length. Will be distributed uniformly among all segments.
@export var rope_length: float = 100: set = _set_length

## Maximum euclidean distance between rope endpoints. Zero or negative for no limitation.
## This is an approximation and not 100% accurate.
## It is intended as a simple way to constraint the rope length when both endpoints are fixed by a RopeHandle.
## The actual length of the rope might differ depending on the number of constraint iterations.
## Fixed points in between are not taken into account.
@export var max_endpoint_distance: float = -1

## (Optional) Allows to distribute the length of rope segment in a non-uniform manner.
## Useful when certain parts of the rope should be more detailed than the rest.
## For example, if it is known that most movement happens at the beginning of the rope, a curve with
Expand Down
37 changes: 37 additions & 0 deletions demo/rope_examples/handles.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,40 @@ script = ExtResource("3_h0p3k")
num_segments = 20
rope_length = 200.0
metadata/_edit_group_ = true

[node name="Label6" type="Label" parent="."]
offset_left = 304.0
offset_top = 511.0
offset_right = 823.0
offset_bottom = 534.0
text = "max_endpoint_distance limits the distance between both rope endpoints."

[node name="Rope5" type="Node2D" parent="."]
position = Vector2(356, 591)
script = ExtResource("3_h0p3k")
max_endpoint_distance = 150.0
fixate_begin = false

[node name="RopeHandleBegin" type="Marker2D" parent="Rope5"]
position = Vector2(-10, -4)
script = ExtResource("1_n2oah")
rope_path = NodePath("..")
rope_position = 0.0
strength = 1.0

[node name="RopeHandleEnd" type="Marker2D" parent="Rope5"]
position = Vector2(204, 22)
script = ExtResource("1_n2oah")
rope_path = NodePath("..")
strength = 1.0

[node name="Rope6" type="Node2D" parent="."]
position = Vector2(636, 564)
script = ExtResource("3_h0p3k")
max_endpoint_distance = 150.0

[node name="RopeHandleEnd" type="Marker2D" parent="Rope6"]
position = Vector2(160, 86)
script = ExtResource("1_n2oah")
rope_path = NodePath("..")
strength = 1.0
60 changes: 41 additions & 19 deletions src/NativeRopeContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ void NativeRopeContext::load_context(Node2D* rope)
gravity_direction = rope->get("gravity_direction");
damping = rope->get("damping");
stiffness = rope->get("stiffness");
max_endpoint_distance = rope->get("max_endpoint_distance");
num_constraint_iterations = rope->get("num_constraint_iterations");
seg_lengths = rope->call("get_segment_lengths");
simulation_weights = rope->get("_simulation_weights");
Expand Down Expand Up @@ -151,30 +152,51 @@ void NativeRopeContext::_simulate_stiffness(PackedVector2Array* velocities) cons
}
}

static void constraint_segment(Vector2* point_a, Vector2* point_b, float weight_a, float weight_b, float seg_length)
{
const Vector2 diff = *point_b - *point_a;
const float distance = diff.length();
const float error = (seg_length - distance) * 0.5f;
const Vector2 dir = error * (diff / distance);

// If one point has a weight < 1.0, the other point must compensate the difference in
// relation to its own weight.
// This is especially relevant with fixate_begin = true or with arbitrary weights = 0.0.
// In that case non-fixed point should be constrained by the whole error distance, not
// just half of it, because the other one can obviously not move.
// It actually works quite fine without this compensation, but this is more correct and
// produces better results.
*point_a -= (weight_a + weight_a * (1.0 - weight_b)) * dir;
*point_b += (weight_b + weight_b * (1.0 - weight_a)) * dir;
}

void NativeRopeContext::_constraint()
{
const bool use_euclid_constraint = max_endpoint_distance > 0;
Vector2* first_point;
Vector2* last_point;
float euclid_constraint_first_weight;
float max_stretch_length_sqr;

if (use_euclid_constraint)
{
first_point = &points[0];
last_point = &points[(int)points.size() - 1];
euclid_constraint_first_weight = fixate_begin ? 0.0 : 1.0;
max_stretch_length_sqr = max_endpoint_distance * max_endpoint_distance;
}

for (int _ = 0; _ < num_constraint_iterations; ++_)
{
for (int i = 0; i < points.size() - 1; ++i)
if (use_euclid_constraint)
{
const Vector2 diff = points[i + 1] - points[i];
const float distance = diff.length();
const float error = (seg_lengths[i] - distance) * 0.5f;
const Vector2 dir = error * (diff / distance);
const float weight = simulation_weights[i];
const float next_weight = simulation_weights[i + 1];

// If one point has a weight < 1.0, the other point must compensate the difference in
// relation to its own weight.
// This is especially relevant with fixate_begin = true or with arbitrary weights = 0.0.
// In that case non-fixed point should be constrained by the whole error distance, not
// just half of it, because the other one can obviously not move.
// It actually works quite fine without this compensation, but this is more correct and
// produces better results.
// points[i] -= weight * dir;
// points[i + 1] += next_weight * dir;
points[i] -= (weight + weight * (1.0 - next_weight)) * dir;
points[i + 1] += (next_weight + next_weight * (1.0 - weight)) * dir;
const float rope_length_sqr = first_point->distance_squared_to(*last_point);

if (rope_length_sqr > max_stretch_length_sqr)
constraint_segment(first_point, last_point, euclid_constraint_first_weight, 1.0, max_endpoint_distance);
}

for (int i = 0; i < points.size() - 1; ++i)
constraint_segment(&points[i], &points[i + 1], simulation_weights[i], simulation_weights[i + 1], seg_lengths[i]);
}
}
1 change: 1 addition & 0 deletions src/NativeRopeContext.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ namespace godot
Vector2 gravity_direction;
float damping;
float stiffness;
float max_endpoint_distance;
int num_constraint_iterations;
Ref<Curve> damping_curve;
bool fixate_begin;
Expand Down

0 comments on commit 9195e42

Please sign in to comment.