Skip to content

Instantly share code, notes, and snippets.

@rutvik110
Last active January 19, 2023 19:11
Show Gist options
  • Save rutvik110/56f4626c95b92b8cf2c95283d4682331 to your computer and use it in GitHub Desktop.
Save rutvik110/56f4626c95b92b8cf2c95283d4682331 to your computer and use it in GitHub Desktop.
An implementation of catenary algorithm in dart to paint a hanging rope.
// An example of simulating a simple rope. For, the construction of rope, catenary algorithm(https://en.wikipedia.org/wiki/Catenary) is used.
// The catenary is a curve that describes the shape of a hanging chain or cable, or the curve described by a freely hanging chain or cable.
// The implementation of catenary algorithm is based on the following js implementation : https://github.com/dulnan/catenary-curve/blob/9cb7e53e2db4bd5c499f5051abde8bfd853d946a/src/main.ts#L254
// In action : https://github.com/rutvik110/Flutter-Animations/tree/master/lib/flutter_design_challenges/ropes
import 'dart:developer' as dev;
import 'dart:math' as math;
import 'package:dart_numerics/dart_numerics.dart' as numerics;
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_animations/flutter_shaders/stripes_shader/stripes.dart';
import 'package:flutter_animations/util/extensions/colot_to_vector.dart';
import 'package:scidart/numdart.dart';
const EPSILON = 1e-6;
class RopesPainter extends CustomPainter {
RopesPainter(
this.startPoint,
this.endPoint,
this.stripes,
);
final Offset endPoint;
final Offset startPoint;
final Shader stripes;
@override
void paint(Canvas canvas, Size size) {
// TODO: implement paint
Paint paint = Paint()
..color = Colors.white
..strokeWidth = 10.0
..strokeJoin = StrokeJoin.round
..strokeCap = StrokeCap.round
..shader = stripes
..style = PaintingStyle.stroke;
final startingPoint =
math.Point(startPoint.dx / size.width, startPoint.dy / size.height);
final finalendPoint =
math.Point(endPoint.dx / size.width, endPoint.dy / size.height);
final curvePoints = getCaternaryCurve(startingPoint, finalendPoint, 1);
final points = curvePoints
.map((e) => Offset(e.x * size.width, (e.y) * size.height))
.toList();
final path = Path();
path.moveTo(points.first.dx, points.first.dy);
for (var point in points) {
path.lineTo(point.dx, point.dy);
}
canvas.drawPath(path, paint);
}
List<math.Point> getCaternaryCurve(
math.Point point1, math.Point point2, double chainLength) {
const segments = 40;
const iterationLimit = 11;
// The curves are reversed
final isFlipped = point1.x > point2.x;
final p1 = isFlipped ? point2 : point1;
final p2 = isFlipped ? point1 : point2;
final h = p2.x - p1.x;
final v = p2.y - p1.y;
final a = -getCatenaryParameter(h.toDouble(), v.toDouble(), chainLength,
iterationLimit, point1, point2);
// Handle NAN/rope being stretched than its original length case
if (a.isNaN) {
return [point1, point2];
}
final x = (a * math.log((chainLength + v) / (chainLength - v)) - h) * 0.5;
final y = a * cosh(x / a);
final offsetX = p1.x - x;
final offsetY = p1.y - y;
final curveData = getCurve(
a,
p1,
p2,
offsetX,
offsetY,
segments,
);
return curveData;
}
List<math.Point> getCurve(
// The catenary parameter.
double a,
// First point.
math.Point p1,
// Second point.
math.Point p2,
double offsetX,
double offsetY,
// How many "parts" the chain should be made of.
int segments,
) {
final data = [
// Calculate the first point on the curve
math.Point(p1.x, a * cosh((p1.x - offsetX) / a) + offsetY),
];
final d = p2.x - p1.x;
final length = segments - 1;
// Calculate the points in between the first and last point
for (var i = 0; i < length; i++) {
final x = p1.x + (d * (i + 0.5)) / length;
final y = a * cosh((x - offsetX) / a) + offsetY;
data.add(math.Point(x, y));
}
// Calculate the last point on the curve
data.add(math.Point(p2.x, a * cosh((p2.x - offsetX) / a) + offsetY));
return data;
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
double getDistanceBetweenPoints(math.Point p1, math.Point p2) {
final diff = getDifferenceTo(p1, p2);
return math.sqrt(math.pow(diff.x, 2) + math.pow(diff.y, 2));
}
math.Point getDifferenceTo(math.Point p1, math.Point p2) {
return math.Point(p1.x - p2.x, p1.y - p2.y);
}
double getCatenaryParameter(
double h,
double v,
double length,
int limit,
math.Point point,
math.Point point2,
) {
final m = math.sqrt(length * length - v * v) / h;
double x = numerics.acosh(m) + 1;
double prevx = -1;
double count = 0;
// Iterate until we find a suitable catenary parameter or reach the iteration
// limit
while ((x - prevx).abs() > EPSILON && count < limit) {
prevx = x;
x = x - (sinh(x) - m * x) / (cosh(x) - m);
count++;
}
return h / (2 * x);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment