refactor: refactored baseboard with arcshapes

pull/87/head
RuiAlonso 4 years ago
parent 4403dbc38e
commit 6cbf4b156f

@ -46,25 +46,23 @@ class Baseboard extends BodyComponent with InitialPosition {
final outerEdgeShapeFixtureDef = FixtureDef(outerEdgeShape); final outerEdgeShapeFixtureDef = FixtureDef(outerEdgeShape);
fixturesDef.add(outerEdgeShapeFixtureDef); fixturesDef.add(outerEdgeShapeFixtureDef);
final upperArcFixtureDefs = Pathway.arc( final upperArcShape = ArcShape(
center: Vector2(1.76 * direction, 3.25), center: Vector2(1.76 * direction, 3.25),
width: 0, arcRadius: 6.1,
radius: 6.1,
angle: arcsAngle, angle: arcsAngle,
rotation: arcsRotation, rotation: arcsRotation,
singleWall: true, );
).createFixtureDefs(); final upperArcFixtureDefs = FixtureDef(upperArcShape);
fixturesDef.addAll(upperArcFixtureDefs); fixturesDef.add(upperArcFixtureDefs);
final lowerArcFixtureDefs = Pathway.arc( final lowerArcShape = ArcShape(
center: Vector2(1.85 * direction, -2.15), center: Vector2(1.85 * direction, -2.15),
width: 0, arcRadius: 4.5,
radius: 4.5,
angle: arcsAngle, angle: arcsAngle,
rotation: arcsRotation, rotation: arcsRotation,
singleWall: true, );
).createFixtureDefs(); final lowerArcFixtureDefs = FixtureDef(lowerArcShape);
fixturesDef.addAll(lowerArcFixtureDefs); fixturesDef.add(lowerArcFixtureDefs);
final bottomRectangle = PolygonShape() final bottomRectangle = PolygonShape()
..setAsBox( ..setAsBox(

@ -8,7 +8,6 @@ export 'jetpack_ramp.dart';
export 'joint_anchor.dart'; export 'joint_anchor.dart';
export 'kicker.dart'; export 'kicker.dart';
export 'launcher_ramp.dart'; export 'launcher_ramp.dart';
export 'pathway.dart';
export 'plunger.dart'; export 'plunger.dart';
export 'ramp_opening.dart'; export 'ramp_opening.dart';
export 'round_bumper.dart'; export 'round_bumper.dart';

@ -1,209 +0,0 @@
import 'package:flame/extensions.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:geometry/geometry.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template pathway}
/// [Pathway] creates lines of various shapes.
///
/// [BodyComponent]s such as a Ball can collide and move along a [Pathway].
/// {@endtemplate}
class Pathway extends BodyComponent with InitialPosition, Layered {
Pathway._({
// TODO(ruialonso): remove color when assets added.
Color? color,
required List<List<Vector2>> paths,
}) : _paths = paths {
paint = Paint()
..color = color ?? const Color.fromARGB(0, 0, 0, 0)
..style = PaintingStyle.stroke;
}
/// Creates a uniform unidirectional (straight) [Pathway].
///
/// Does so with two [ChainShape] separated by a [width]. Can
/// be rotated by a given [rotation] in radians.
///
/// If [singleWall] is true, just one [ChainShape] is created.
factory Pathway.straight({
Color? color,
required Vector2 start,
required Vector2 end,
required double width,
double rotation = 0,
bool singleWall = false,
}) {
final paths = <List<Vector2>>[];
// TODO(ruialonso): Refactor repetitive logic
final firstWall = [
start.clone(),
end.clone(),
].map((vector) => vector..rotate(rotation)).toList();
paths.add(firstWall);
if (!singleWall) {
final secondWall = [
start + Vector2(width, 0),
end + Vector2(width, 0),
].map((vector) => vector..rotate(rotation)).toList();
paths.add(secondWall);
}
return Pathway._(
color: color,
paths: paths,
);
}
/// Creates an arc [Pathway].
///
/// The [angle], in radians, specifies the size of the arc. For example, two
/// pi returns a complete circumference.
///
/// Does so with two [ChainShape] separated by a [width]. Which can be
/// rotated by a given [rotation] in radians.
///
/// The outer radius is specified by [radius], whilst the inner one is
/// equivalent to the [radius] minus the [width].
///
/// If [singleWall] is true, just one [ChainShape] is created.
factory Pathway.arc({
Color? color,
required Vector2 center,
required double width,
required double radius,
required double angle,
double rotation = 0,
bool singleWall = false,
}) {
final paths = <List<Vector2>>[];
// TODO(ruialonso): Refactor repetitive logic
final outerWall = calculateArc(
center: center,
radius: radius,
angle: angle,
offsetAngle: rotation,
);
paths.add(outerWall);
if (!singleWall) {
final innerWall = calculateArc(
center: center,
radius: radius - width,
angle: angle,
offsetAngle: rotation,
);
paths.add(innerWall);
}
return Pathway._(
color: color,
paths: paths,
);
}
/// Creates a bezier curve [Pathway].
///
/// Does so with two [ChainShape] separated by a [width]. Which can be
/// rotated by a given [rotation] in radians.
///
/// First and last [controlPoints] set the beginning and end of the curve,
/// inner points between them set its final shape.
///
/// If [singleWall] is true, just one [ChainShape] is created.
factory Pathway.bezierCurve({
Color? color,
required List<Vector2> controlPoints,
required double width,
double rotation = 0,
bool singleWall = false,
}) {
final paths = <List<Vector2>>[];
// TODO(ruialonso): Refactor repetitive logic
final firstWall = calculateBezierCurve(controlPoints: controlPoints)
.map((vector) => vector..rotate(rotation))
.toList();
paths.add(firstWall);
if (!singleWall) {
final secondWall = calculateBezierCurve(
controlPoints: controlPoints
.map((vector) => vector + Vector2(width, -width))
.toList(),
).map((vector) => vector..rotate(rotation)).toList();
paths.add(secondWall);
}
return Pathway._(
color: color,
paths: paths,
);
}
/// 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 = <List<Vector2>>[];
// 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<List<Vector2>> _paths;
/// Constructs different [ChainShape]s to form the [Pathway] shape.
List<FixtureDef> createFixtureDefs() {
final fixturesDef = <FixtureDef>[];
for (final path in _paths) {
final chain = ChainShape()..createChain(path);
fixturesDef.add(FixtureDef(chain));
}
return fixturesDef;
}
@override
Body createBody() {
final bodyDef = BodyDef()..position = initialPosition;
final body = world.createBody(bodyDef);
createFixtureDefs().forEach(body.createFixture);
return body;
}
}

@ -1,243 +0,0 @@
// ignore_for_file: cascade_invocations, prefer_const_constructors
import 'dart:math' as math;
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/game.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(Forge2DGame.new);
group('Pathway', () {
const width = 50.0;
group('straight', () {
flameTester.test(
'loads correctly',
(game) async {
final pathway = Pathway.straight(
start: Vector2(10, 10),
end: Vector2(20, 20),
width: width,
);
await game.ready();
await game.ensureAdd(pathway);
expect(game.contains(pathway), isTrue);
},
);
group('color', () {
flameTester.test(
'has transparent color by default when no color is specified',
(game) async {
final pathway = Pathway.straight(
start: Vector2(10, 10),
end: Vector2(20, 20),
width: width,
);
await game.ready();
await game.ensureAdd(pathway);
expect(game.contains(pathway), isTrue);
expect(pathway.paint, isNotNull);
expect(
pathway.paint.color,
equals(Color.fromARGB(0, 0, 0, 0)),
);
},
);
flameTester.test(
'has a color when is specified',
(game) async {
const defaultColor = Colors.blue;
final pathway = Pathway.straight(
color: defaultColor,
start: Vector2(10, 10),
end: Vector2(20, 20),
width: width,
);
await game.ready();
await game.ensureAdd(pathway);
expect(game.contains(pathway), isTrue);
expect(pathway.paint, isNotNull);
expect(pathway.paint.color.value, equals(defaultColor.value));
},
);
});
group('body', () {
flameTester.test(
'is static',
(game) async {
final pathway = Pathway.straight(
start: Vector2(10, 10),
end: Vector2(20, 20),
width: width,
);
await game.ready();
await game.ensureAdd(pathway);
expect(pathway.body.bodyType, equals(BodyType.static));
},
);
});
group('fixtures', () {
flameTester.test(
'has only one ChainShape when singleWall is true',
(game) async {
final pathway = Pathway.straight(
start: Vector2(10, 10),
end: Vector2(20, 20),
width: width,
singleWall: true,
);
await game.ready();
await game.ensureAdd(pathway);
expect(pathway.body.fixtures.length, 1);
final fixture = pathway.body.fixtures[0];
expect(fixture, isA<Fixture>());
expect(fixture.shape.shapeType, equals(ShapeType.chain));
},
);
flameTester.test(
'has two ChainShape when singleWall is false (default)',
(game) async {
final pathway = Pathway.straight(
start: Vector2(10, 10),
end: Vector2(20, 20),
width: width,
);
await game.ready();
await game.ensureAdd(pathway);
expect(pathway.body.fixtures.length, 2);
for (final fixture in pathway.body.fixtures) {
expect(fixture, isA<Fixture>());
expect(fixture.shape.shapeType, equals(ShapeType.chain));
}
},
);
});
});
group('arc', () {
flameTester.test(
'loads correctly',
(game) async {
final pathway = Pathway.arc(
center: Vector2.zero(),
width: width,
radius: 100,
angle: math.pi / 2,
);
await game.ready();
await game.ensureAdd(pathway);
expect(game.contains(pathway), isTrue);
},
);
group('body', () {
flameTester.test(
'is static',
(game) async {
final pathway = Pathway.arc(
center: Vector2.zero(),
width: width,
radius: 100,
angle: math.pi / 2,
);
await game.ready();
await game.ensureAdd(pathway);
expect(pathway.body.bodyType, equals(BodyType.static));
},
);
});
});
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),
Vector2(50, 0),
Vector2(0, 50),
Vector2(50, 50),
];
flameTester.test(
'loads correctly',
(game) async {
final pathway = Pathway.bezierCurve(
controlPoints: controlPoints,
width: width,
);
await game.ready();
await game.ensureAdd(pathway);
expect(game.contains(pathway), isTrue);
},
);
group('body', () {
flameTester.test(
'is static',
(game) async {
final pathway = Pathway.bezierCurve(
controlPoints: controlPoints,
width: width,
);
await game.ready();
await game.ensureAdd(pathway);
expect(pathway.body.bodyType, equals(BodyType.static));
},
);
});
});
});
}
Loading…
Cancel
Save