Reputation: 1
I'm using a Matrix4 to simulate a wall in 3D. I'm having trouble recalculating the X and Y rotation of the matrix after the object is resized.
With the flat object, the resizing works according to the resizing of the parent.
When the object is rotated, the matrix loses perspective.
Does anyone have any tips on how to find the correct calculation?
Below are some examples, as well as a small code snippet that can reproduce the problem in Dartpad.
Example of plain wall:
Example of rotated wall:
Code (runs on dartpad):
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: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorSchemeSeed: Colors.blue,
),
home: const MyHomePage(title: 'Matrix4 Resizing'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({
super.key,
required this.title,
});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String exampleImage =
'https://firebasestorage.googleapis.com/v0/b/carlens-375f2.appspot.com/o/users%2FZvRl4WQMWqa9zsf0UlquGDFfRhE3%2Fuploads%2F1729225149799000.png?alt=media&token=e363445a-6a3c-40ce-a0a0-06f9fbeeebb4';
double _w = 500; //initial width
double _h = 375; //initial height .75 from width
Rect _wallRect = Rect.fromLTRB(0, 48, 1090, 215); //initial wall position
Offset _wallOffset = Offset(110, 0.5); //resize doesn't work with rotation
//Offset _wallOffset = Offset.zero; //resize works with zero rotation
void _resize(double val) {
double oldW = _w;
double newW = oldW + val;
double scale = newW / oldW;
Rect oldWallRect = _wallRect;
setState(() {
_w = newW;
_h = _w * .75; //keep 4:3 ratio
_wallRect = Rect.fromLTRB(
oldWallRect.left,
oldWallRect.top * scale,
oldWallRect.right * scale,
oldWallRect.bottom * scale,
);
});
//rotation calc not working
double wx = _wallRect.width / oldWallRect.width;
double wy = _wallRect.height / oldWallRect.height;
var rotateX = Offset(cos(0) - 1, sin(0)) * wx / 2;
var rotateY = Offset(-sin(0), cos(0) - 1) * wy / 2;
setState(() {
_wallOffset += Offset(rotateX.distance, rotateY.distance);
});
//rotation calc not working
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
//container
Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.red,
width: 0,
),
),
width: _w,
height: _h,
child: Stack(
alignment: const AlignmentDirectional(0, 0),
children: [
//bgImage
Image.network(
width: double.infinity,
height: double.infinity,
exampleImage,
fit: BoxFit.fitWidth,
),
// wall
Positioned.fromRect(
rect: _wallRect,
child: Container(
transform: Matrix4.identity()
..setEntry(3, 2, 0.0008)
..rotateX(0.01 * _wallOffset.dy)
..rotateY(-0.01 * _wallOffset.dx),
transformAlignment: FractionalOffset.centerLeft,
decoration: BoxDecoration(
border: Border.all(
color: Colors.yellow,
width: 1,
),
color: Colors.yellow.withOpacity(0.5),
),
),
),
],
),
),
],
),
),
Padding(
padding: const EdgeInsetsDirectional.fromSTEB(0, 0, 0, 50),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
style: ButtonStyle(
foregroundColor:
WidgetStateProperty.all<Color>(Colors.blue),
overlayColor: WidgetStateProperty.resolveWith<Color?>(
(Set<WidgetState> states) {
if (states.contains(WidgetState.hovered)) {
return Colors.blue.withOpacity(0.04);
}
if (states.contains(WidgetState.focused) ||
states.contains(WidgetState.pressed)) {
return Colors.blue.withOpacity(0.12);
}
return null;
},
),
),
onPressed: () => _resize(-100),
child: Text('<<< Decrement Size'),
),
TextButton(
style: ButtonStyle(
foregroundColor:
WidgetStateProperty.all<Color>(Colors.blue),
overlayColor: WidgetStateProperty.resolveWith<Color?>(
(Set<WidgetState> states) {
if (states.contains(WidgetState.hovered)) {
return Colors.blue.withOpacity(0.04);
}
if (states.contains(WidgetState.focused) ||
states.contains(WidgetState.pressed)) {
return Colors.blue.withOpacity(0.12);
}
return null;
},
),
),
onPressed: () => _resize(100),
child: Text('Increment Size >>>'),
),
],
),
),
],
),
),
);
}
}
I can resize the wall using the parent's resizing scale. I haven't found the correct calculation for rotation yet.
Upvotes: 0
Views: 19