You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
pinball/packages/geometry/lib/src/geometry.dart

151 lines
4.2 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

import 'dart:math' as math;
import 'package:vector_math/vector_math_64.dart';
/// Calculates all [Vector2]s of a circumference.
///
/// A circumference can be achieved by specifying a [center] and a [radius].
/// In addition, a semi-circle can be achieved by specifying its [angle] and an
/// [offsetAngle] (both in radians).
///
/// The higher the [precision], the more [Vector2]s will be calculated;
/// achieving a more rounded arc.
///
/// For more information read: https://en.wikipedia.org/wiki/Trigonometric_functions.
List<Vector2> calculateArc({
required Vector2 center,
required double radius,
required double angle,
double offsetAngle = 0,
int precision = 100,
}) {
final stepAngle = angle / (precision - 1);
final points = <Vector2>[];
for (var i = 0; i < precision; i++) {
final x = center.x + radius * math.cos((stepAngle * i) + offsetAngle);
final y = center.y - radius * math.sin((stepAngle * i) + offsetAngle);
final point = Vector2(x, y);
points.add(point);
}
return points;
}
/// Calculates all [Vector2]s of an ellipse.
///
/// An ellipse can be achieved by specifying a [center], a [majorRadius] and a
/// [minorRadius].
///
/// The higher the [precision], the more [Vector2]s will be calculated;
/// achieving a more rounded ellipse.
///
/// For more information read: https://en.wikipedia.org/wiki/Ellipse.
List<Vector2> calculateEllipse({
required Vector2 center,
required double majorRadius,
required double minorRadius,
int precision = 100,
}) {
assert(
0 < minorRadius && minorRadius <= majorRadius,
'smallRadius ($minorRadius) and bigRadius ($majorRadius) must be in '
'range 0 < smallRadius <= bigRadius',
);
final stepAngle = 2 * math.pi / (precision - 1);
final points = <Vector2>[];
for (var i = 0; i < precision; i++) {
final x = center.x + minorRadius * math.cos(stepAngle * i);
final y = center.y - majorRadius * math.sin(stepAngle * i);
final point = Vector2(x, y);
points.add(point);
}
return points;
}
/// Calculates all [Vector2]s of a bezier curve.
///
/// A bezier curve of [controlPoints] that say how to create this curve.
///
/// First and last points specify the beginning and the end respectively
/// of the curve. The inner points specify the shape of the curve and
/// its turning points.
///
/// The [step] must be between zero and one (inclusive), indicating the
/// precision to calculate the curve.
///
/// For more information read: https://en.wikipedia.org/wiki/B%C3%A9zier_curve
List<Vector2> calculateBezierCurve({
required List<Vector2> controlPoints,
double step = 0.01,
}) {
assert(
0 <= step && step <= 1,
'Step ($step) must be in range 0 <= step <= 1',
);
assert(
controlPoints.length >= 2,
'At least 2 control points needed to create a bezier curve',
);
var t = 0.0;
final n = controlPoints.length - 1;
final points = <Vector2>[];
do {
var x = 0.0;
var y = 0.0;
for (var i = 0; i <= n; i++) {
final point = controlPoints[i];
x += binomial(n, i) * math.pow(1 - t, n - i) * math.pow(t, i) * point.x;
y += binomial(n, i) * math.pow(1 - t, n - i) * math.pow(t, i) * point.y;
}
points.add(Vector2(x, y));
t = t + step;
} while (t <= 1);
return points;
}
/// Calculates the binomial coefficient of 'n' and 'k'.
///
/// For more information read: https://en.wikipedia.org/wiki/Binomial_coefficient
num binomial(num n, num k) {
assert(0 <= k && k <= n, 'k ($k) and n ($n) must be in range 0 <= k <= n');
if (k == 0 || n == k) {
return 1;
} else {
return factorial(n) / (factorial(k) * factorial(n - k));
}
}
/// Calculate the factorial of 'n'.
///
/// For more information read: https://en.wikipedia.org/wiki/Factorial
num factorial(num n) {
assert(n >= 0, 'Factorial is not defined for negative number n ($n)');
if (n == 0 || n == 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}
/// Arithmetic mean position of all the [Vector2]s in a polygon.
///
/// For more information read: https://en.wikipedia.org/wiki/Centroid
Vector2 centroid(List<Vector2> vertices) {
assert(vertices.isNotEmpty, 'Vertices must not be empty');
final sum = vertices.reduce((a, b) => a + b);
return sum / vertices.length.toDouble();
}