Skip to content

Commit

Permalink
feat: Added Rotate3DDecorator (#1805)
Browse files Browse the repository at this point in the history
This PR creates a new Decorator for rotating a component in 3D.
  • Loading branch information
st-pasha authored Jul 21, 2022
1 parent 112acf2 commit f05194c
Show file tree
Hide file tree
Showing 11 changed files with 262 additions and 3 deletions.
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;
}
}

0 comments on commit f05194c

Please sign in to comment.