-
-
Notifications
You must be signed in to change notification settings - Fork 899
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Added Rotate3DDecorator (#1805)
This PR creates a new Decorator for rotating a component in 3D.
- Loading branch information
Showing
11 changed files
with
262 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
104
packages/flame/test/rendering/rotate3d_decorator_test.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |