+void
+Spring::set_inverse_stretch_strength (Real f)
+{
+ if (isinf (f) || isnan (f) || f < 0)
+ programming_error ("insane spring constant");
+ else
+ inverse_stretch_strength_ = f;
+
+ update_blocking_force ();
+}
+
+void
+Spring::set_inverse_compress_strength (Real f)
+{
+ if (isinf (f) || isnan (f) || f < 0)
+ programming_error ("insane spring constant");
+ else
+ inverse_compress_strength_ = f;
+
+ update_blocking_force ();
+}
+
+void
+Spring::set_blocking_force (Real f)
+{
+ if (isinf (f) || isnan (f))
+ {
+ programming_error ("insane blocking force");
+ return;
+ }
+
+ blocking_force_ = -infinity_f;
+ min_distance_ = length (f);
+ update_blocking_force ();
+}
+
+void
+Spring::set_default_strength ()
+{
+ set_default_stretch_strength ();
+ set_default_compress_strength ();
+}
+
+void
+Spring::set_default_compress_strength ()
+{
+ inverse_compress_strength_ = (distance_ >= min_distance_) ? distance_ - min_distance_ : 0;
+ update_blocking_force ();
+}
+
+void
+Spring::set_default_stretch_strength ()
+{
+ inverse_stretch_strength_ = distance_;
+}
+
+Real
+Spring::length (Real f) const
+{
+ Real force = max (f, blocking_force_);
+ Real inv_k = force < 0.0 ? inverse_compress_strength_ : inverse_stretch_strength_;
+
+ if (isinf (force))
+ {
+ programming_error ("cruelty to springs");
+ force = 0.0;
+ }
+
+ // There is a corner case here: if min_distance_ is larger than
+ // distance_ but the spring is fixed, then inv_k will be zero
+ // and we need to make sure that we return min_distance_.
+ return max (min_distance_, distance_ + force * inv_k);
+}