Rotating around custom pivot point - matrix

I don't really know linear algebra, I'm trying to learn it because of this issue now.
I'm pretty sure it's kind of simple question for those who knows linear algebra.
I'll try to explain and show some pictures, videos and code to describe it better.
First, my goal is pretty simple at first glance.
Is to create this sort of animation
Now I managed to get almost there
Widget getWidget(Type type)
{
Matrix4 _pmat(num pv) {
return new Matrix4(
1.0, 0.0, 0.0, 0.0, //
0.0, 1.0, 0.0, 0.0, //
0.0, 0.0, 1.0, pv * 0.001, //
0.0, 0.0, 0.0, 1.0,
);
}
double delta = _panDown ? _delta : _animationOffset.value;
Matrix4 perspective = _pmat(0.35);
if (type == Type.NEXT)
{
delta -= MAX_DELTA;
}
double xOffset = ((delta / 70) / 2.5) * (deviceWidth / 2);
double zOffset = (delta / 70 / (math.pi - 0.55)).abs() * 180.0;
double rotateY = (math.pi - (delta / 3) * math.pi / 180) - math.pi;
if (type == Type.NEXT)
{
xOffset = ((delta / 70) / 2.5) * (deviceWidth);
zOffset *= 2;
}
perspective.translate(xOffset, 0.0, zOffset);
double angleOffset = delta * -0.5;
return new Transform(
alignment: FractionalOffset.center,
transform: perspective.scaled(1.0, 1.0, 1.0)
..rotateX(0.0)
..rotateY(rotateY)
..rotateZ(0.0),
child: new Container(color: Colors.red),
);
}
As you can see I'm currently moving each "page" with "magic numbers" and I call it magic numbers because I'm pretty dam noob at linear algebra and what I've done here is with the little understanding I have + trial and error. Clearly not the right way.
I have the delta which is basically just an offset taken from the user's GestureDetector to just play with it.
Now, with all the samples of this cube transition I saw let's say for example in CSS, they just rotated/translated each individual side. And then moved/rotated the "camera" or the parent I guess ?
I tried to do that method as well:
Like this:
Widget getWidget(Type type)
{
AlignmentGeometry alignmentGeometry;
Matrix4 matrix4;
switch (type)
{
case Type.PREVIOUS:
alignmentGeometry = FractionalOffset.center;
matrix4 = Matrix4.identity()
..setEntry(3, 2, 0.001) // perspective
..rotateY(-90.0 * math64.degrees2Radians)
..translate(deviceWidth / 2, 0.0, deviceWidth / 2);
break;
case Type.CURRENT:
alignmentGeometry = FractionalOffset.center;
matrix4 = Matrix4.identity()
..setEntry(3, 2, 0.001);
break;
case Type.NEXT:
alignmentGeometry = FractionalOffset.center;
matrix4 = Matrix4.identity()
..setEntry(3, 2, 0.001) // perspective
..rotateY(90.0 * math64.degrees2Radians)
..translate(-deviceWidth / 2, 0.0, deviceWidth / 2);
break;
}
return new Transform(
alignment: alignmentGeometry,
transform: matrix4,
child: new Container(
color: type == Type.NEXT ? Colors.green : type == Type.PREVIOUS ? Colors.red : Colors.black.withOpacity(0.5),
),
);
}
And then let's simply rotate the parent right ?
Widget build(BuildContext context)
{
List<Widget> widgets = [];
widgets.add(getWidget(Type.PREVIOUS));
widgets.add(getWidget(Type.CURRENT));
widgets.add(getWidget(Type.NEXT));
return new Scaffold(
key: _scaffoldKey,
body: new Container(
color: Colors.black,
child: new GestureDetector(
child: new Container(
color: Colors.white,
child: new Transform(
alignment: FractionalOffset.center,
transform: Matrix4.identity()
..setEntry(3, 2, 0.0001)
..rotateY(45.0 * math64.degrees2Radians),
child: new Stack(
children: widgets,
),
)
),
)
)
);
}
No:
Having it 90 degrees will make it all disappear.
Now, I do understand that I need sort of "pivot rotation point" or change the "origin" or "anchor point" not sure even how to call it.
So to picture it, let's say for example we also have a bottom side. so I want the center of that bottom side (let's say for example in a cube). So it will look like this:
Now if I'll rotate that "origin point" in the Y axis I should get the right animation right ?

Related

How do I align my Neumorphic widgets in concentric circles Flutter?

Im using the flutter_neumorphics dependency https://pub.dev/packages/flutter_neumorphic
to make my circular Neumorphic containers. However I'm facing an issue when Im trying to resize it.
I want the outer circle to be smaller and more compact.
Here is the code I've tried:
Widget build(BuildContext context) {
final percentage = 50.0;
return Stack(
children: <Widget>[
Align(
child: Neumorphic(
boxShape: NeumorphicBoxShape.circle(),
padding: EdgeInsets.all(80),
style: NeumorphicStyle(
depth: NeumorphicTheme.embossDepth(context),
),
child: CustomPaint(
painter: NeuProgressPainter(
circleWidth: 20,
completedPercentage: percentage,
defaultCircleColor: Colors.transparent,
),
child: Center(),
),
),
),
Align(
child: Neumorphic(
boxShape: NeumorphicBoxShape.circle(),
padding: EdgeInsets.all(80),
style: NeumorphicStyle(
color: Colors.white,
depth: NeumorphicTheme.depth(context),
),
),
),
],
);
}
Where the NeuProgressPainter is defined as:
class NeuProgressPainter extends CustomPainter {
//
Color defaultCircleColor;
Color percentageCompletedCircleColor;
double completedPercentage;
double circleWidth;
NeuProgressPainter(
{this.defaultCircleColor,
this.percentageCompletedCircleColor,
this.completedPercentage,
this.circleWidth});
getPaint(Color color) {
return Paint()
..color = color
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke
..strokeWidth = circleWidth;
}
#override
void paint(Canvas canvas, Size size) {
Paint defaultCirclePaint = getPaint(defaultCircleColor);
Offset center = Offset(size.width / 2, size.height / 2);
double radius = min(size.width / 2, size.height / 2);
Rect boundingSquare = Rect.fromCircle(center: center, radius: radius);
paint(
List<Color> colors,
) {
final Gradient gradient = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomRight,
colors: colors,
);
return Paint()
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke
..strokeWidth = circleWidth
..shader = gradient.createShader(boundingSquare);
}
canvas.drawCircle(center, radius, defaultCirclePaint);
double arcAngle = 2 * pi * (completedPercentage / 100);
canvas.drawArc(
Rect.fromCircle(center: center, radius: radius),
-pi / 2,
arcAngle,
false,
paint(
[
kcolor,
kDarkOrange,
kOrange,
],
),
);
}
#override
bool shouldRepaint(CustomPainter painter) {
return true;
}
}
This is what my output looks like:
Im trying to achieve something similar to this inspiration:
How about this?
Padding(
padding: const EdgeInsets.all(80.0),
child: Align(
child: Neumorphic(
boxShape: NeumorphicBoxShape.circle(),
padding: EdgeInsets.all(20),
style: NeumorphicStyle(
depth: NeumorphicTheme.embossDepth(context),
),
child: CustomPaint(
painter: NeuProgressPainter(
circleWidth: 20,
completedPercentage: percentage,
defaultCircleColor: Colors.transparent,
),
child: Center(),
),
),
),
),

Flutter: low performance when zoom custom painter

I'm try paint many TextSpan on custom painter. Everything ok, but when I try zoom painter it's become very slow. What's way to speed up?
Scale widget with custom paint:
#override
Widget build(BuildContext context) {
return GestureTransformable(
size: sz,
initialScale: 0.3,
maxScale: 40.0,
minScale: 0.01,
boundaryRect: Rect.fromLTWH(
-sz.width * 10 / 2,
-sz.height * 10 / 2,
sz.width * 10,
sz.height * 10,
),
child: RepaintBoundary(
child: CustomPaint(
painter: CustomPaintSpans(),
),
)
);
}
and custom painter:
#override
void paint(Canvas canvas, Size size) {
double delta = 10.0;
double pixelSize = 10.0;
for (int j = 0; j < 100; ++j) {
for (int i = 0; i < 100; ++i) {
double startX = delta + i * (pixelSize + 1);
double startY = delta + j * (pixelSize + 1);
////////
TextSpan textSpan = new TextSpan(
text: "$i",
style: TextStyle(fontSize: 1, color: Colors.grey, fontWeight: FontWeight.w800));
TextPainter tp = TextPainter(
textScaleFactor: 10,
text: textSpan,
textAlign: TextAlign.center,
textDirection: TextDirection.ltr);
tp.layout();
Offset offset;
offset = Offset(startX - pixelSize * 0.5, startY - pixelSize * 0.5);
tp.paint(canvas, offset);
///////////
}
}
performance
app screen

Custom Shape in Flutter

I'm trying to draw a shape like this with some text and data in it.
I am using CustomPaint for this
CustomPaint(
painter: new RightBox(context),
child: container()
),
Container container() {
return Container(
height: 200,
width: 200,
color: Colors.black,
margin: EdgeInsets.only(left: 20),
child: Center(child: Text("Hi Khushal",
style: TextStyle(
color: Colors.white
),
),)
);
}
class RightBox extends CustomPainter {
RightBox(this.context);
BuildContext context;
#override
void paint(Canvas canvas, Size size) {
size = MediaQuery.of(context).size;
Paint paint = Paint()
..color = Colors.black;
Path path = Path()
..moveTo(size.width * 0.02 + size.width * 0.5, size.height * 0.85)
..lineTo(size.width * 0.05 + size.width * 0.5, size.height * 0.70)
..lineTo(size.width * 0.45 + size.width * 0.5, size.height * 0.67)
..lineTo(size.width * 0.48 + size.width * 0.5, size.height * 0.85);
path.close();
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(RightBox oldDelegate) {
return true;
}
}
I want the text to be shown in the bottom right CustomPaint Widget,
I am passing a child Container to the CustomPaint but instead, I'm getting a widget positioned separately in the stack.
I know I'm doing something wrong, please help me achieve the UI.
Your painter is taking the full size of the screen. Hence, you need to align the container to put it inside the painter. Here is one way to do it. There are several ways to do it.
You put your container and painter inside a stack widget. Align the container according to your needs. Make sure to give a few padding and margin to get a more accurate result.
Stack(
children: <Widget>[
Align(
alignment: Alignment.bottomRight,
child: Container(
height: 200,
width: 200,
alignment: Alignment.bottomRight,
child: Center(
child: Text(
"Hi Khushal",
style: TextStyle(color: Colors.black),
),
)),
),
CustomPaint(
painter: RightBox(context),
),
],
),

Flutter code creating laggy animation. What is wrong here?

I have created a flutter widget that moves the circle with accelerometer. It is very laggy as I have to use setState to change the position of the circle when phone is moved. Is there an alternative to creating this?
I have used AnimatedBuilder here but not sure how that can change the position of circle when device is moved smoothly.
class _AnimationWidgetState extends State<AnimationWidget>
with TickerProviderStateMixin {
AnimationController _animeController;
Animation _anime;
double x = 0.0, y = 0.0;
#override
void initState() {
super.initState();
_animeController =
AnimationController(vsync: this, duration: const Duration(seconds: 2));
_anime = Tween(begin: 0.5, end: 0.5).animate(
CurvedAnimation(parent: _animeController, curve: Curves.ease));
accelerometerEvents.listen((AccelerometerEvent event) {
var a = ((event.x * 100).round() / 100).clamp(-1.0, 1.0) * -1;
var b = ((event.y * 100).round() / 100).clamp(-1.0, 1.0);
if ((x - a).abs() > 0.02 || (y - b).abs() > 0.02) {
setState(() {
x = a; y = b;
});
}
});
}
#override
void dispose() {
_animeController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
_animeController.forward();
final double width = MediaQuery.of(context).size.width;
final double height = MediaQuery.of(context).size.height;
return AnimatedBuilder(
animation: _animeController,
builder: (context, child) {
return Scaffold(
body: Transform(
transform: Matrix4.translationValues(
_anime.value * width * x, _anime.value * height * y, 0.0),
child: Center(
child: CircleAvatar(
radius: 15.0,
backgroundColor: Colors.green,
),
),
),
);
},
);
}
}
Animation is not at all smooth. This is because I have to use setState but the movement of circle is working as desired.
The whole purpose of using AnimationController is to listen to its events - that's what AnimatedBuilder does and rebuilds its subtree accordingly.
I will post here my overall recommendations on what to change in the code.
Remove setState - that's what makes your entire layout rebuild all over again, i.e. lags.
Also trigger _animeController listeners, i.e. AnimatedBuilder in your case - to rebuild itself.
accelerometerEvents.listen((AccelerometerEvent event) {
var a = ((event.x * 100).round() / 100).clamp(-1.0, 1.0) * -1;
var b = ((event.y * 100).round() / 100).clamp(-1.0, 1.0);
if ((x - a).abs() > 0.02 || (y - b).abs() > 0.02) {
x = a; y = b;
_animeController.value = _animeController.value; // Trigger controller's listeners
}
});
Start animation in initState, instead of build. This is the second thing that produces lags in your case. .forward triggers rebuild of your widgets, which leads to an infinite loop.
#override
void initState() {
super.initState();
_animeController.forward();
}
Use child property of your AnimatedBuilder to save up resources on rebuilding the avatar block every time. Also I'm not sure what Scaffold is here for - let's remove it if it's not needed.
AnimatedBuilder(
animation: _animeController,
builder: (context, child) => Transform(
transform: Matrix4.translationValues(_anime.value * width * x, _anime.value * height * y, 0.0),
child: child,
),
child: Center(
child: CircleAvatar(
radius: 15.0,
backgroundColor: Colors.green,
),
),
);
Follow Official Animations Tutorial to master Flutter animations.
Let me know if this helps.

rotate 3D on X in Flutter

I've been working with Flutter rotation,
new Matrix4.identity()
..rotateX(degrees * 3.1415927 / 180),
but, the problem, I want it to be similar to the diagram below.
can I achieve a 3D-like rotation on the x-axis with Flutter?
even if there is a mapping from 3D to 2D or there are alternatives
that would get the same result.
thanks in advance.
Example image in OpenCV: How to calculate perspective transform for OpenCV from rotation angles?
thanks to this discussion, and this repo, and after more than a day seeking the answer,
static Matrix4 _pmat(num pv) {
return new Matrix4(
1.0, 0.0, 0.0, 0.0, //
0.0, 1.0, 0.0, 0.0, //
0.0, 0.0, 1.0, pv * 0.001, //
0.0, 0.0, 0.0, 1.0,
);
}
Matrix4 perspective = _pmat(1.0);
// then use it
new Center(
child: new Transform(
child: new FittedBox(
fit: BoxFit.fill,
child: LogoWidget(),
),
alignment: FractionalOffset.center,
transform: perspective.scaled(1.0, 1.0, 1.0)
..rotateX(math.pi - degrees * math.pi / 180)
..rotateY(0.0)
..rotateZ(0.0)
),
);
here is the result image
please read a little theory about this subject.
You can use Transform widget to apply a matrix onto it's child.
Here's an example combining Transform with the animation framework to rotate on X, Y, and Z directions.
import 'dart:math' as math;
import 'package:flutter/material.dart';
void main() {
runApp(
new MaterialApp(
home: new Home(),
),
);
}
class Home extends StatefulWidget {
#override
_HomeState createState() => new _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
AnimationController animationController;
Animation<double> rotateX;
Animation<double> rotateY;
Animation<double> rotateZ;
#override
initState() {
super.initState();
animationController = new AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat();
print('bar');
rotateX = new Tween<double>(
begin: .0,
end: 1.0,
).animate(new CurvedAnimation(
parent: animationController,
curve: new Interval(.0, 1 / 3),
));
rotateY = new Tween<double>(
begin: .0,
end: 1.0,
).animate(new CurvedAnimation(
parent: animationController,
curve: new Interval(1 / 3, 2 / 3),
));
rotateZ = new Tween<double>(
begin: .0,
end: .5,
).animate(new CurvedAnimation(
parent: animationController,
curve: new Interval(2 / 3, 1.0),
));
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new AnimatedBuilder(
animation: animationController,
builder: (context, child) {
final card = new SizedBox(
width: 42.0,
height: 42.0,
child: new Card(
color:
animationController.value >= 1 / 6 && animationController.value <= 3 / 6 ? Colors.blue : Colors.red,
),
);
return new Transform(
transform: new Matrix4.rotationX(rotateX.value * math.pi)
..multiply(new Matrix4.rotationY(rotateY.value * math.pi))
..multiply(new Matrix4.rotationZ(rotateZ.value * math.pi)),
alignment: Alignment.center,
child: card,
);
},
),
),
);
}
}
class MyTransform extends StatefulWidget {
const MyTransform({Key? key}) : super(key: key);
#override
_MyTransformState createState() => _MyTransformState();
}
class _MyTransformState extends State<MyTransform> {
double x = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.003) // col = 2, row = 3 & 0.003 = depth perception in the Z direction
..rotateX(x), // (Both are equal because both are 4D identity matrix)
// transform: Matrix4(
// 1, 0, 0, 0,
// 0, 1, 0, 0,
// 0, 0, 1, 0.003,
// 0, 0, 0, 1,
// )..rotateX(x),
alignment: FractionalOffset.center,
child: GestureDetector(
onPanUpdate: (details) {
setState(() {
x = x + details.delta.dy / 100;
});
},
child: Container(
color: Colors.red,
height: 200.0,
width: 200.0,
),
),
),
),
);
}
}

Resources