Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added Rotate3DDecorator #1805

Merged
merged 40 commits into from
Jul 21, 2022
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
eb0d219
Added the Decorator class
st-pasha Jul 2, 2022
7f7a3c0
Added PaintDecorator
st-pasha Jul 2, 2022
bd4ec46
define constructors for PaintDecorator
st-pasha Jul 3, 2022
cd9e3a7
added export file
st-pasha Jul 3, 2022
7cfbcd7
wip
st-pasha Jul 3, 2022
4e243da
feat: Added size parameter for testGolden()
st-pasha Jul 3, 2022
e10e8a5
rename key
st-pasha Jul 3, 2022
416192e
format
st-pasha Jul 3, 2022
2cb2524
format
st-pasha Jul 3, 2022
c1501b3
Merge remote-tracking branch 'upstream/ps.golden-test-size' into ps.d…
st-pasha Jul 3, 2022
6a15298
added tests
st-pasha Jul 3, 2022
f7b23b6
one more test
st-pasha Jul 3, 2022
180a616
update last test
st-pasha Jul 3, 2022
bb25294
Merge branch 'main' into ps.decorators
st-pasha Jul 3, 2022
0328f6e
feat: New colours to pallete.dart (#1783)
vikilinho Jul 4, 2022
9d7b446
perf!: Game.images/assets are now same as Flame.images/assets by defa…
st-pasha Jul 7, 2022
81193c9
fix: Correct the imports for the padracing -> dartpad script (#1786)
spydon Jul 8, 2022
4082a6a
chore(release): publish packages (#1789)
spydon Jul 8, 2022
e0b587c
feat: Add vector projection and inversion (#1787)
spydon Jul 9, 2022
dda9d49
feat: add `children` argument to `SpriteComponent.fromImage` (#1793)
wolfenrain Jul 9, 2022
635d49c
docs: replace images in README with absolute path (#1796)
immadisairaj Jul 12, 2022
e3ecf3b
Merge branch 'main' into ps.decorators
st-pasha Jul 13, 2022
f303e3c
Added HasDecorator mixin
st-pasha Jul 13, 2022
c696610
added a test
st-pasha Jul 13, 2022
21ddde8
Added Flower class
st-pasha Jul 15, 2022
e8750a4
Added doc page for Decorators
st-pasha Jul 15, 2022
b24f67e
rename tinted->tint
st-pasha Jul 16, 2022
f258849
copied Decorator class
st-pasha Jul 17, 2022
aa29e1a
copied HasDecorator mixin
st-pasha Jul 17, 2022
5ffcad3
Rotate3DDecorator
st-pasha Jul 17, 2022
8982f78
export file
st-pasha Jul 17, 2022
1d147b5
doc comments
st-pasha Jul 17, 2022
3f8c36d
added tests
st-pasha Jul 18, 2022
05d0eae
format
st-pasha Jul 18, 2022
e812a9f
Merge branch 'st-pasha-ps.decorators' into ps.rotate3d
st-pasha Jul 18, 2022
3f726d0
doc page for Rotate3DDecorator
st-pasha Jul 18, 2022
127f0bf
format
st-pasha Jul 18, 2022
834211f
minor refactor
st-pasha Jul 18, 2022
446fdfa
Merge branch 'main' into ps.rotate3d
st-pasha Jul 18, 2022
41d9279
Merge branch 'main' into ps.rotate3d
st-pasha Jul 21, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions doc/flame/examples/lib/decorator_rotate3d.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import 'package:doc_flame_examples/flower.dart';
import 'package:flame/experimental.dart';
import 'package:flame/game.dart';
import 'package:flame/rendering.dart';

class DecoratorRotate3DGame extends FlameGame with HasTappableComponents {
@override
Future<void> onLoad() async {
var step = 0;
add(
Flower(
size: 100,
position: canvasSize / 2,
decorator: Rotate3DDecorator()
..center = canvasSize / 2
..perspective = 0.01,
onTap: (flower) {
step++;
final decorator = flower.decorator! as Rotate3DDecorator;
if (step == 1) {
decorator.angleY = -0.8;
} else if (step == 2) {
decorator.angleX = 1.0;
} else if (step == 3) {
decorator.angleZ = 0.2;
} else if (step == 4) {
decorator.angleX = 10;
} else if (step == 5) {
decorator.angleY = 2;
} else {
decorator
..angleX = 0
..angleY = 0
..angleZ = 0;
step = 0;
}
},
)..onTapUp(),
);
}
}
10 changes: 8 additions & 2 deletions doc/flame/examples/lib/flower.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@ import 'dart:ui';

import 'package:flame/components.dart';
import 'package:flame/experimental.dart';
import 'package:flame/rendering.dart';

const tau = 2 * pi;

class Flower extends PositionComponent with TapCallbacks, HasDecorator {
Flower({required double size, void Function(Flower)? onTap, super.position})
: _onTap = onTap,
Flower({
required double size,
void Function(Flower)? onTap,
Decorator? decorator,
super.position,
}) : _onTap = onTap,
super(size: Vector2.all(size), anchor: Anchor.center) {
this.decorator = decorator;
final radius = size * 0.38;
_paths.add(_makePath(radius * 1.4, 6, -0.05, 0.8));
_paths.add(_makePath(radius, 6, 0.25, 1.5));
Expand Down
6 changes: 5 additions & 1 deletion doc/flame/examples/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:html'; // ignore: avoid_web_libraries_in_flutter

import 'package:doc_flame_examples/decorator_blur.dart';
import 'package:doc_flame_examples/decorator_grayscale.dart';
import 'package:doc_flame_examples/decorator_rotate3d.dart';
import 'package:doc_flame_examples/decorator_tint.dart';
import 'package:doc_flame_examples/drag_events.dart';
import 'package:doc_flame_examples/tap_events.dart';
Expand All @@ -27,7 +28,10 @@ void main() {
case 'decorator_grayscale':
game = DecoratorGrayscaleGame();
break;
case 'decorator_tinted':
case 'decorator_rotate3d':
game = DecoratorRotate3DGame();
break;
case 'decorator_tint':
game = DecoratorTintGame();
break;
}
Expand Down
32 changes: 32 additions & 0 deletions doc/flame/rendering/decorators.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,38 @@ Possible uses:
- tint the scene deep blue during the night time;


### Rotate3DDecorator

```{flutter-app}
:sources: ../flame/examples
:page: decorator_rotate3d
:show: widget infobox
:width: 180
:height: 160
```

This decorator applies a 3D rotation to the underlying component. You can specify the angles of the
rotation, as well as the pivot point and the amount of perspective distortion to apply.

The decorator also supplies the `isFlipped` property, which allows you to determine whether the
component is currently being viewed from the front side or from the back. This is useful if you want
to draw a component whose appearance is different in the front and in the back.

```dart
final decorator = Rotate3DDecorator(
center: component.center,
angleX: rotationAngle,
perspective: 0.002,
);
```

Possible uses:
- a card that can be flipped over;
- pages in a book;
- transitions between app routes;
- 3d falling particles such as snowflakes or leaves.


## Using decorators

### HasDecorator mixin
Expand Down
1 change: 1 addition & 0 deletions packages/flame/lib/rendering.dart
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export 'src/rendering/decorator.dart' show Decorator;
export 'src/rendering/paint_decorator.dart' show PaintDecorator;
export 'src/rendering/rotate3d_decorator.dart' show Rotate3DDecorator;
2 changes: 2 additions & 0 deletions packages/flame/lib/src/rendering/decorator.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:ui';

import 'package:flame/src/rendering/paint_decorator.dart';
import 'package:flame/src/rendering/rotate3d_decorator.dart';

/// [Decorator] is an abstract class that encapsulates a particular visual
/// effect that should apply to drawing commands wrapped by this class.
Expand All @@ -16,6 +17,7 @@ import 'package:flame/src/rendering/paint_decorator.dart';
///
/// The following implementations are available:
/// - [PaintDecorator]
/// - [Rotate3DDecorator]
abstract class Decorator {
/// Applies visual effect while [draw]ing on the [canvas].
///
Expand Down
69 changes: 69 additions & 0 deletions packages/flame/lib/src/rendering/rotate3d_decorator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import 'dart:math';
import 'dart:ui';

import 'package:flame/src/rendering/decorator.dart';
import 'package:vector_math/vector_math_64.dart';

/// [Rotate3DDecorator] treats the underlying component as if it was a flat
/// sheet of paper, and applies a 3D rotation to it.
///
/// The angles of rotation can be changed dynamically, allowing you to rotate
/// the content continuously at the desired angular speeds.
class Rotate3DDecorator extends Decorator {
Rotate3DDecorator({
Vector2? center,
this.angleX = 0.0,
this.angleY = 0.0,
this.angleZ = 0.0,
this.perspective = 0.001,
}) : center = center ?? Vector2.zero();

/// The center of rotation, in the **parent** coordinate space.
Vector2 center;

/// Angle of rotation around the X axis. This rotation is usually described as
/// "vertical".
double angleX;

/// Angle of rotation around the Y axis. This rotation is typically described
/// as "horizontal".
double angleY;

/// Angle of rotation around the Z axis. This is a regular "2D" rotation
/// because it occurs entirely inside the plane in which the component is
/// normally drawn.
double angleZ;

/// The strength of the perspective effect. In other words, how much the
/// elements that are "behind" the canvas are shrunk, and those in front of
/// it are expanded.
double perspective;

/// Returns `true` if the component is currently being rendered from its
/// back side, and `false` if it shows the front side.
///
/// The "front" side is the one displayed at `angleX = angleY = 0`, and the
/// "back" side is shows if the component is rotated 180º degree around either
/// the X or Y axis.
bool get isFlipped {
const tau = 2 * pi;
final phaseX = (angleX / tau - 0.25) % 1.0;
final phaseY = (angleY / tau - 0.25) % 1.0;
return (phaseX > 0.5) ^ (phaseY > 0.5);
}

@override
void apply(void Function(Canvas) draw, Canvas canvas) {
canvas.save();
canvas.translate(center.x, center.y);
final matrix = Matrix4.identity()
..setEntry(3, 2, perspective)
..rotateX(angleX)
..rotateY(angleY)
..rotateZ(angleZ)
..translate(-center.x, -center.y);
canvas.transform(matrix.storage);
draw(canvas);
canvas.restore();
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
104 changes: 104 additions & 0 deletions packages/flame/test/rendering/rotate3d_decorator_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import 'dart:ui';

import 'package:flame/components.dart';
import 'package:flame/rendering.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
group('Rotate3DDecorator', () {
testGolden(
'Rotation around X axis',
(game) async {
for (var angle = 0.0; angle <= 1.5; angle += 0.5) {
game.add(
DecoratedRectangle(
position: Vector2(20, 30),
size: Vector2(60, 100),
paint: Paint()..color = const Color(0x9dde0445),
decorator: Rotate3DDecorator(
center: Vector2(50, 80),
angleX: angle,
perspective: 0.005,
),
),
);
}
},
size: Vector2(100, 160),
goldenFile: '../_goldens/rotate3d_decorator_1.png',
);

testGolden(
'Rotation around Y axis',
(game) async {
for (var angle = 0.0; angle <= 1.5; angle += 0.5) {
game.add(
DecoratedRectangle(
position: Vector2(20, 30),
size: Vector2(60, 100),
paint: Paint()..color = const Color(0x9dde0445),
decorator: Rotate3DDecorator(
center: Vector2(50, 80),
angleY: angle,
perspective: 0.005,
),
),
);
}
},
size: Vector2(100, 160),
goldenFile: '../_goldens/rotate3d_decorator_2.png',
);

testGolden(
'Rotation around all axes',
(game) async {
game.add(
DecoratedRectangle(
position: Vector2(20, 30),
size: Vector2(60, 100),
paint: Paint()..color = const Color(0xff199f2b),
decorator: Rotate3DDecorator(
center: Vector2(50, 80),
angleX: 0.7,
angleY: 1.0,
angleZ: 0.5,
perspective: 0.005,
),
),
);
},
size: Vector2(100, 160),
goldenFile: '../_goldens/rotate3d_decorator_3.png',
);

test('isFlipped', () {
final decorator = Rotate3DDecorator();
expect(decorator.isFlipped, false);
decorator.angleZ = 2.0;
expect(decorator.isFlipped, false);
decorator.angleX = 2.0;
expect(decorator.isFlipped, true);
decorator.angleY = 2.0;
expect(decorator.isFlipped, false);
decorator.angleY = -0.5;
expect(decorator.isFlipped, true);
decorator.angleY = -1.5;
expect(decorator.isFlipped, true);
decorator.angleY = -1.6;
expect(decorator.isFlipped, false);
});
});
}

class DecoratedRectangle extends RectangleComponent with HasDecorator {
DecoratedRectangle({
super.position,
super.size,
super.paint,
Decorator? decorator,
}) {
this.decorator = decorator;
}
}