I’m trying to create a pseudo 3d representation of a paper plane with flutter without using any 3d library only transformations I want to accomplish something like that :
This is what I have achieved so far:
- The perspective is not working correctly. When rotating the plane, the faces that come to the foreground should make the parts behind them invisible, but this is not happening. How could this be implemented?
- How can I slightly rotate the wings of the plane at the back so that the plane resembles the image?
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Paper plane',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const PaperPlane(title: '3D Paper plane'),
);
}
}
class PaperPlane extends StatefulWidget {
const PaperPlane({super.key, required this.title});
final String title;
@override
State<PaperPlane> createState() => _PaperPlaneState();
}
class _PaperPlaneState extends State<PaperPlane> {
Offset offset = Offset.zero;
Matrix4 _applyPerspective(Matrix4 matrix) {
const double perspective = 0.003; // Ajusta este valor para la profundidad
matrix.setEntry(3, 2, perspective);
return matrix;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
backgroundColor: Colors.blue,
),
body: Stack(
children: [
Center(
child: Transform(
key: const ValueKey('plane'),
transform: _applyPerspective(Matrix4.identity()
..rotateX(-offset.dy * pi / 180)
..rotateY(-offset.dx * pi / 180)),
alignment: Alignment.center,
child: Stack(
children: [
Transform(
transform: Matrix4.identity()..translate(-50.0, 0, 0.0),
alignment: Alignment.center,
child: CustomPaint(
size: const Size(100, 200),
painter:
TrianglePainter(color: Colors.blue, rotate: true),
),
),
Transform(
transform: Matrix4.identity()..translate(50.0, 0.0, 0.0),
alignment: Alignment.center,
child: CustomPaint(
size: const Size(100, 200),
painter:
TrianglePainter(color: Colors.blue, rotate: false),
),
),
Transform(
transform: _applyPerspective(Matrix4.identity()
..translate(0.0, 0.0, -50.0)
..rotateY(90 * pi / 180)),
alignment: Alignment.center,
child: CustomPaint(
size: const Size(100, 200),
painter:
TrianglePainter(color: Colors.red, rotate: false),
),
),
Transform(
transform: _applyPerspective(Matrix4.identity()
..translate(0.0, 0.0, -50.0)
..rotateY(90 * pi / 180)),
alignment: Alignment.center,
child: CustomPaint(
size: const Size(100, 200),
painter:
TrianglePainter(color: Colors.orange, rotate: false),
),
),
],
),
),
),
Positioned.fill(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: GestureDetector(
onPanUpdate: (details) {
setState(() {
offset = Offset((offset.dx + details.delta.dx) % 360,
(offset.dy + details.delta.dy) % 360);
});
},
child: Container(color: Colors.transparent),
),
),
Text('x: ${offset.dx} y: ${offset.dy}')
],
),
),
],
),
);
}
}
class TrianglePainter extends CustomPainter {
final Color color;
final bool rotate;
TrianglePainter({required this.color, required this.rotate});
@override
void paint(Canvas canvas, Size size) {
final Paint fillPaint = Paint()
..color = color
..style = PaintingStyle.fill;
final Paint borderPaint = Paint()
..color = Colors.black
..style = PaintingStyle.stroke
..strokeWidth = 3.0;
final Path path = Path();
if (!rotate) {
// Definir los puntos del triángulo rectángulo
path
..moveTo(0, size.height) // Iniciar en la esquina inferior izquierda
..lineTo(size.width, size.height) // Ir a la esquina inferior derecha
..lineTo(0, 0) // Ir a la esquina superior izquierda
..close(); // Cerrar el triángulo
} else {
// Definir los puntos del triángulo del ala derecha
path
..moveTo(size.width, 0) // Vértice superior derecho
..lineTo(0, size.height) // Vértice inferior izquierdo
..lineTo(size.width, size.height) // Vértice inferior derecho
..close(); // Cerrar el triángulo
}
// Dibujar el relleno del triángulo
canvas.drawPath(path, fillPaint);
// Dibujar el borde del triángulo
canvas.drawPath(path, borderPaint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
class LeftWingPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final Paint fillPaint = Paint()
..color = Colors.blue
..style = PaintingStyle.fill;
final Paint borderPaint = Paint()
..color = Colors.black
..style = PaintingStyle.stroke
..strokeWidth = 2.0;
// Definir los puntos del triángulo del ala izquierda
final Path path = Path()
..moveTo(0, 0) // Vértice superior izquierdo
..lineTo(size.width, size.height) // Vértice inferior derecho
..lineTo(0, size.height) // Vértice inferior izquierdo
..close(); // Cerrar el triángulo
// Dibujar el triángulo
canvas.drawPath(path, fillPaint);
canvas.drawPath(path, borderPaint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
class RightWingPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final Paint fillPaint = Paint()
..color = Colors.blue
..style = PaintingStyle.fill;
final Paint borderPaint = Paint()
..color = Colors.black
..style = PaintingStyle.stroke
..strokeWidth = 2.0;
// Definir los puntos del triángulo del ala derecha
final Path path = Path()
..moveTo(size.width, 0) // Vértice superior derecho
..lineTo(0, size.height) // Vértice inferior izquierdo
..lineTo(size.width, size.height) // Vértice inferior derecho
..close(); // Cerrar el triángulo
// Dibujar el triángulo
canvas.drawPath(path, fillPaint);
canvas.drawPath(path, borderPaint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}