@@ -15,9 +15,14 @@ static float get_point_perc(int64_t index, const PackedVector2Array& points)
1515 return points.size () > 0 ? static_cast <float >(index) / static_cast <float >(points.size () - 1 ) : 0 ;
1616}
1717
18+ static Vector2 exp_decay (Vector2 value, Vector2 target, float damping_factor, double delta)
19+ {
20+ return value.lerp (target, (float )(1.0 - exp (-damping_factor * delta)));
21+ }
22+
1823static Vector2 damp_vec (Vector2 value, float damping_factor, double delta)
1924{
20- return value. lerp ( VECTOR_ZERO, ( float )( 1.0 - exp (- damping_factor * delta)) );
25+ return exp_decay (value, VECTOR_ZERO, damping_factor, delta);
2126}
2227
2328
@@ -67,6 +72,9 @@ void NativeRopeContext::load_context(Node2D* rope)
6772 collision_damping = rope->get (" collision_damping" );
6873 report_contact_points = rope->get (" report_contact_points" );
6974 resolve_collisions_while_constraining = rope->get (" resolve_collisions_while_constraining" );
75+
76+ if (data_buffer.size () < points.size ())
77+ data_buffer.resize (points.size () * 2 );
7078}
7179
7280void NativeRopeContext::simulate (double delta)
@@ -82,7 +90,16 @@ void NativeRopeContext::simulate(double delta)
8290 points[0 ] = rope->get_global_position ();
8391 }
8492
93+ // Calculate new velocities and swap point buffers
8594 _simulate_velocities (delta);
95+ std::swap (oldpoints, points);
96+
97+ // Compute new point positions
98+ for (int64_t i = 0 ; i < points.size (); ++i)
99+ points[i] = oldpoints[i] + simulation_weights[i] * data_buffer[i];
100+
101+ // Constraint step
102+ _simulate_stiffness (delta);
86103 _constraint (delta);
87104
88105 if (!resolve_collisions_while_constraining)
@@ -107,41 +124,22 @@ void NativeRopeContext::_writeback()
107124
108125void NativeRopeContext::_simulate_velocities (double delta)
109126{
110- const int first_idx = fixate_begin ? 1 : 0 ;
111- const int size = (int )points.size ();
112-
113- // NOTE: The following is a little ugly but it reduces memory usage by reusing existing buffers.
114- // It is also faster than allocating a new temporary buffer and also likely more cache efficient.
115-
116- // oldpoints is no longer needed after initial velocity computation, so we reuse it to store velocities.
117- PackedVector2Array& velocities = oldpoints;
118- // Afterwards we use oldpoints to store the new points and finally swap points and oldpoints
119- PackedVector2Array& new_points = oldpoints;
120-
121- // Compute velocities and apply damping + gravity
122127 const bool use_damping_curve = damping_curve.is_valid () && damping_curve->get_point_count () > 0 ;
123128 const Vector2 frame_gravity = gravity_direction * static_cast <float >(gravity * delta);
124129
125- for (int i = first_idx; i < size; ++i)
130+ // Init velocities using delta + damping + gravity
131+ for (int64_t i = 0 ; i < points.size (); ++i)
126132 {
127133 const Vector2 vel = points[i] - oldpoints[i];
128134 const float dampmult = use_damping_curve ? damping_curve->sample_baked (get_point_perc (i, points)) : 1 .0f ;
129- velocities[i] = damp_vec (vel, damping * dampmult, delta);
130- velocities[i] += frame_gravity;
135+ data_buffer[i] = damp_vec (vel, damping * dampmult, delta) + frame_gravity;
131136 }
132137
133138 // Apply other forces
134- _simulate_wind (&velocities);
135- _simulate_stiffness (delta, &velocities);
136-
137- // Move points according to velocity
138- for (int i = first_idx; i < size; ++i)
139- new_points[i] = points[i] + simulation_weights[i] * velocities[i];
140-
141- std::swap (oldpoints, points);
139+ _simulate_wind ();
142140}
143141
144- void NativeRopeContext::_simulate_wind (PackedVector2Array* velocities) const
142+ void NativeRopeContext::_simulate_wind ()
145143{
146144 if (wind.is_null () || !wind->get_enable_wind () || wind->get_noise ().is_null ())
147145 return ;
@@ -154,43 +152,54 @@ void NativeRopeContext::_simulate_wind(PackedVector2Array* velocities) const
154152 {
155153 const float noise = wind->get_noise ()->get_noise_3d (points[i].x , points[i].y , time);
156154 const Vector2 noise_velocity = orth_strength * noise;
157- (*velocities) [i] += wind_velocity + noise_velocity;
155+ data_buffer [i] += wind_velocity + noise_velocity;
158156 }
159157}
160158
161- void NativeRopeContext::_simulate_stiffness (double delta, PackedVector2Array* velocities) const
159+ void NativeRopeContext::_simulate_stiffness (double delta)
162160{
163- if (stiffness_params->get_stiffness () <= 0 )
161+ if (stiffness_params. is_null () || stiffness_params ->get_stiffness () <= 0 )
164162 return ;
165163
166164 const auto num_points = points.size ();
165+ uint8_t num_passes = 1 ; // Averaging the force in bidirectional stiffness seems to make the rope more stable
167166
168- if (stiffness_params->get_stiffness_method () == RopeStiffnessParameters::Legacy)
167+ // Clear buffer as it is used for accumulating stiffness forces.
168+ for (int64_t i = 0 ; i < num_points; ++i)
169+ data_buffer[i] = VECTOR_ZERO;
170+
171+ if (stiffness_params->get_stiffness_method () == RopeStiffnessParameters::Forward)
169172 {
170173 const Vector2 parent_seg_dir = rope->get_global_transform ().basis_xform (VECTOR_DOWN).normalized ();
171- _simulate_stiffness_segment (delta, velocities, 1 , num_points, parent_seg_dir); // Start at second and stop at last point
174+ _simulate_stiffness_segment (delta, 1 , num_points, parent_seg_dir); // Start at second and stop at last point
172175 }
173176 else
174177 {
175178 const Vector2 end_parent = (points[num_points - 2 ] - points[num_points - 1 ]).normalized ();
179+ num_passes = 2 ;
176180
177181 if (fixate_begin)
178182 {
179183 // TODO: Make direction configurable, e.g. an enum to select the mode ("transform", "gravity", "custom").
180184 const Vector2 parent_seg_dir = rope->get_global_transform ().basis_xform (VECTOR_DOWN).normalized ();
181- _simulate_stiffness_segment (delta, velocities, 1 , num_points, parent_seg_dir); // Start at second and stop at last point
182- _simulate_stiffness_segment (delta, velocities, num_points - 2 , 0 , end_parent); // Start at second last and stop before first point (because it's fixed)
185+ _simulate_stiffness_segment (delta, 1 , num_points, parent_seg_dir); // Start at second and stop at last point
186+ _simulate_stiffness_segment (delta, num_points - 2 , 0 , end_parent); // Start at second last and stop before first point (because it's fixed)
183187 }
184188 else
185189 {
186190 const Vector2 begin_parent = (points[1 ] - points[0 ]).normalized ();
187- _simulate_stiffness_segment (delta, velocities, 1 , num_points, begin_parent); // Start at second and stop at last point
188- _simulate_stiffness_segment (delta, velocities, num_points - 2 , -1 , end_parent); // Start at second last and stop at first point
191+ _simulate_stiffness_segment (delta, 1 , num_points, begin_parent); // Start at second and stop at last point
192+ _simulate_stiffness_segment (delta, num_points - 2 , -1 , end_parent); // Start at second last and stop at first point
189193 }
190194 }
195+
196+ const float stiffness_damping = stiffness_params->get_stiffness_damping ();
197+
198+ for (int64_t i = 0 ; i < num_points; ++i)
199+ points[i] += damp_vec (data_buffer[i] / num_passes, stiffness_damping, delta);
191200}
192201
193- void NativeRopeContext::_simulate_stiffness_segment (double /* delta*/ , PackedVector2Array* velocities, int64_t idx_from, int64_t idx_stop, Vector2 last_stiffness_dir) const
202+ void NativeRopeContext::_simulate_stiffness_segment (double delta, int64_t idx_from, int64_t idx_stop, Vector2 last_stiffness_dir)
194203{
195204 // NOTE: oldpoints should not be used here, see comments in simulate_velocities().
196205
@@ -200,16 +209,12 @@ void NativeRopeContext::_simulate_stiffness_segment(double /*delta*/, PackedVec
200209 const auto stiffness_bend_curve = stiffness_params->get_stiffness_bend_curve ();
201210 const bool use_stiffness_curve = stiffness_curve.is_valid () && stiffness_curve->get_point_count () > 0 ;
202211 const bool use_bend_curve = stiffness_bend_curve.is_valid () && stiffness_bend_curve->get_point_count () > 0 ;
203- const int64_t idx_step = idx_from <= idx_stop ? 1 : -1 ;
212+ const bool iterating_forward = idx_from <= idx_stop;
213+ const int64_t idx_step = iterating_forward ? 1 : -1 ;
214+ const int64_t seglength_idx_offset = iterating_forward ? -1 : 0 ;
204215
205216 for (int64_t i = idx_from; i != idx_stop; i += idx_step)
206217 {
207- // NOTE: Asked a physicist to confirm this computation is physically accurate.
208- // He mentioned that, while it is technically correct, there is a material-dependent limit
209- // how far an object can bend before bending properties (stiffness) changes.
210- // E.g. a material might be less inclined to snap back into place at smaller bend angles, or
211- // a material might stop bending at some point, i.e. when it breaks.
212- //
213218 // | parent_seg_dir ---> parent_seg_dir.orthogonal()
214219 // | \
215220 // V \ seg_dir
@@ -218,22 +223,21 @@ void NativeRopeContext::_simulate_stiffness_segment(double /*delta*/, PackedVec
218223 // V V
219224 // / force_dir
220225 // V
221- //
222- const auto last_idx = i - idx_step;
226+
227+ const int64_t last_idx = i - idx_step;
223228 const Vector2 seg_dir = (points[i] - points[last_idx]).normalized ();
224229 const float angle = seg_dir.angle_to (last_stiffness_dir); // angle is signed and can be used to determine the force direction
225- const float bend_factor = Math::absf (angle / 3 . 1415f );
230+ const float bend_factor = Math::absf (angle / ( float )Math_PI );
226231 const float scale_curve_pos = get_point_perc (idx_step >= 0 ? i : (points.size () - 1 - i), points);
227232 const float scale = use_stiffness_curve ? stiffness_curve->sample_baked (scale_curve_pos) : 1 .0f ;
228- // If no curve specified, use quadratic scale by default
229- const float bend_scale = (use_bend_curve ? stiffness_bend_curve->sample_baked (bend_factor) : bend_factor * bend_factor);
233+ const float bend_scale = use_bend_curve ? stiffness_bend_curve->sample_baked (bend_factor) : 1 .0f ;
230234
231- // The force directs orthogonal to the current segment
232- const Vector2 force_dir = Math::sign (-angle) * seg_dir. orthogonal () ;
233- force += force_dir * stiffness * scale * bend_scale;
235+ const Vector2 target_pos = points[last_idx] + last_stiffness_dir * seg_lengths[i + seglength_idx_offset];
236+ const Vector2 diff = target_pos - points[i] ;
237+ force += exp_decay (VECTOR_ZERO, diff, stiffness * scale * bend_scale, delta) ;
234238
235- // `simulation_weights` will be applied later in `_simulate_velocities()`
236- (*velocities) [i] += force;
239+ // In bidirectional mode points need to be buffered to prevent the first pass affecting the second
240+ data_buffer [i] += force * simulation_weights[i] ;
237241
238242 // Carry velocity over to the next segment. `simulation_weights` must be applied now to not carry over a wrong value.
239243 force *= simulation_weights[i];
0 commit comments