From f1b35d3eb217ed4941c9c4523309eeb0b942b77c Mon Sep 17 00:00:00 2001 From: Rui Miguel Alonso Date: Wed, 23 Mar 2022 23:08:15 +0100 Subject: [PATCH] feat: create ellipses from geometry (#84) * feat: create ellipses from geometry * test: geometry test for ellipse * feat: removed required angle and added tests * test: completed tests for geometry * chore: unused import * Update lib/game/components/pathway.dart Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> * refactor: renaming params * chore: missed test saved Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> --- lib/game/components/pathway.dart | 40 ++++++++++++++ lib/game/pinball_game.dart | 1 - packages/geometry/lib/src/geometry.dart | 53 +++++++++++++++---- packages/geometry/test/src/geometry_test.dart | 40 ++++++++++++++ test/game/components/pathway_test.dart | 36 +++++++++++++ 5 files changed, 159 insertions(+), 11 deletions(-) diff --git a/lib/game/components/pathway.dart b/lib/game/components/pathway.dart index 8604e0f3..819ed5f4 100644 --- a/lib/game/components/pathway.dart +++ b/lib/game/components/pathway.dart @@ -144,6 +144,46 @@ class Pathway extends BodyComponent with InitialPosition, Layered { ); } + /// Creates an ellipse [Pathway]. + /// + /// Does so with two [ChainShape]s separated by a [width]. Can + /// be rotated by a given [rotation] in radians. + /// + /// If [singleWall] is true, just one [ChainShape] is created. + factory Pathway.ellipse({ + Color? color, + required Vector2 center, + required double width, + required double majorRadius, + required double minorRadius, + double rotation = 0, + bool singleWall = false, + }) { + final paths = >[]; + + // TODO(ruialonso): Refactor repetitive logic + final outerWall = calculateEllipse( + center: center, + majorRadius: majorRadius, + minorRadius: minorRadius, + ).map((vector) => vector..rotate(rotation)).toList(); + paths.add(outerWall); + + if (!singleWall) { + final innerWall = calculateEllipse( + center: center, + majorRadius: majorRadius - width, + minorRadius: minorRadius - width, + ).map((vector) => vector..rotate(rotation)).toList(); + paths.add(innerWall); + } + + return Pathway._( + color: color, + paths: paths, + ); + } + final List> _paths; /// Constructs different [ChainShape]s to form the [Pathway] shape. diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index 44a7ec01..3ce7fd77 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -1,5 +1,4 @@ // ignore_for_file: public_member_api_docs - import 'dart:async'; import 'package:flame/extensions.dart'; import 'package:flame/input.dart'; diff --git a/packages/geometry/lib/src/geometry.dart b/packages/geometry/lib/src/geometry.dart index 8574bc73..6975f8cb 100644 --- a/packages/geometry/lib/src/geometry.dart +++ b/packages/geometry/lib/src/geometry.dart @@ -23,10 +23,45 @@ List calculateArc({ final points = []; for (var i = 0; i < precision; i++) { - final xCoord = center.x + radius * math.cos((stepAngle * i) + offsetAngle); - final yCoord = center.y - radius * math.sin((stepAngle * i) + offsetAngle); + final x = center.x + radius * math.cos((stepAngle * i) + offsetAngle); + final y = center.y - radius * math.sin((stepAngle * i) + offsetAngle); - final point = Vector2(xCoord, yCoord); + 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 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 = []; + 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); } @@ -63,17 +98,15 @@ List calculateBezierCurve({ final points = []; do { - var xCoord = 0.0; - var yCoord = 0.0; + var x = 0.0; + var y = 0.0; for (var i = 0; i <= n; i++) { final point = controlPoints[i]; - xCoord += - binomial(n, i) * math.pow(1 - t, n - i) * math.pow(t, i) * point.x; - yCoord += - binomial(n, i) * math.pow(1 - t, n - i) * math.pow(t, i) * point.y; + 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(xCoord, yCoord)); + points.add(Vector2(x, y)); t = t + step; } while (t <= 1); diff --git a/packages/geometry/test/src/geometry_test.dart b/packages/geometry/test/src/geometry_test.dart index 5c33d70f..7a49b2b2 100644 --- a/packages/geometry/test/src/geometry_test.dart +++ b/packages/geometry/test/src/geometry_test.dart @@ -33,6 +33,46 @@ void main() { }); }); + group('calculateEllipse', () { + test('returns by default 100 points as indicated by precision', () { + final points = calculateEllipse( + center: Vector2.zero(), + majorRadius: 100, + minorRadius: 50, + ); + expect(points.length, 100); + }); + + test('returns as many points as indicated by precision', () { + final points = calculateEllipse( + center: Vector2.zero(), + majorRadius: 100, + minorRadius: 50, + precision: 50, + ); + expect(points.length, 50); + }); + + test('fails if radius not in range', () { + expect( + () => calculateEllipse( + center: Vector2.zero(), + majorRadius: 100, + minorRadius: 150, + ), + throwsA(isA()), + ); + expect( + () => calculateEllipse( + center: Vector2.zero(), + majorRadius: 100, + minorRadius: 0, + ), + throwsA(isA()), + ); + }); + }); + group('calculateBezierCurve', () { test('fails if step not in range', () { expect( diff --git a/test/game/components/pathway_test.dart b/test/game/components/pathway_test.dart index 03b67c62..63e74d4d 100644 --- a/test/game/components/pathway_test.dart +++ b/test/game/components/pathway_test.dart @@ -165,6 +165,42 @@ void main() { }); }); + group('ellipse', () { + flameTester.test( + 'loads correctly', + (game) async { + final pathway = Pathway.ellipse( + center: Vector2.zero(), + width: width, + majorRadius: 150, + minorRadius: 70, + ); + await game.ready(); + await game.ensureAdd(pathway); + + expect(game.contains(pathway), isTrue); + }, + ); + + group('body', () { + flameTester.test( + 'is static', + (game) async { + final pathway = Pathway.ellipse( + center: Vector2.zero(), + width: width, + majorRadius: 150, + minorRadius: 70, + ); + await game.ready(); + await game.ensureAdd(pathway); + + expect(pathway.body.bodyType, equals(BodyType.static)); + }, + ); + }); + }); + group('bezier curve', () { final controlPoints = [ Vector2(0, 0),