From 2c8afdefc0b7baf90ead0c11bfddc2677bba0b7d Mon Sep 17 00:00:00 2001 From: jvenin Date: Thu, 2 May 2024 20:51:54 +0200 Subject: [PATCH] feat: add translations into viewer --- lib/classes/bouding_box.dart | 8 +- lib/classes/rotation.dart | 11 +- .../utils/mouse_movement_notifier.dart | 160 +++++++++++++----- lib/widgets/viewer/model_drawer.dart | 27 ++- lib/widgets/viewer/viewer.dart | 4 +- 5 files changed, 162 insertions(+), 48 deletions(-) diff --git a/lib/classes/bouding_box.dart b/lib/classes/bouding_box.dart index ef010e6..d3e6e2d 100644 --- a/lib/classes/bouding_box.dart +++ b/lib/classes/bouding_box.dart @@ -9,11 +9,14 @@ class BoundingBoxModel { required this.x, required this.y, required this.z, - }); + }) { + _maxOfCoordinates = maxOfCoordinates; + } late BoxCoordinate x; late BoxCoordinate y; late BoxCoordinate z; + double? _maxOfCoordinates; BoundingBoxModel.zero() { x = (min: 0, max: 1); @@ -22,6 +25,9 @@ class BoundingBoxModel { } double get maxOfCoordinates { + if (_maxOfCoordinates != null) { + return _maxOfCoordinates!; + } final xMax = math.max(x.max.abs(), x.min.abs()); final yMax = math.max(y.max.abs(), y.min.abs()); final zMax = math.max(z.max.abs(), z.min.abs()); diff --git a/lib/classes/rotation.dart b/lib/classes/rotation.dart index 21be559..570ec9d 100644 --- a/lib/classes/rotation.dart +++ b/lib/classes/rotation.dart @@ -3,10 +3,15 @@ import 'package:vector_math/vector_math.dart'; class Transformation { Transformation(); + Vector3 translation = Vector3.zero(); Quaternion quaternion = Quaternion.identity(); double scaleFactor = 1; Matrix4 matrix = Matrix4.identity(); + void setTranslation(double x, double y) { + translation = Vector3(x, y, 0); + } + void setQuaternion( double xRotation, double yRotation, @@ -18,7 +23,7 @@ class Transformation { } void composeMatrix() { - matrix = Matrix4.compose(Vector3.zero(), quaternion, Vector3.all(scaleFactor)); + matrix = Matrix4.compose(translation, quaternion, Vector3.all(scaleFactor)); } @override @@ -28,7 +33,9 @@ class Transformation { @override bool operator ==(Object other) => - other is Transformation && other.quaternion == quaternion && other.scaleFactor == scaleFactor; + other is Transformation && + other.quaternion == quaternion && + other.scaleFactor == scaleFactor; @override int get hashCode => quaternion.hashCode; diff --git a/lib/widgets/utils/mouse_movement_notifier.dart b/lib/widgets/utils/mouse_movement_notifier.dart index 4560f3a..a79159b 100644 --- a/lib/widgets/utils/mouse_movement_notifier.dart +++ b/lib/widgets/utils/mouse_movement_notifier.dart @@ -2,80 +2,162 @@ import 'dart:math'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:paraworld_gsf_viewer/widgets/utils/buttons.dart'; +import 'package:paraworld_gsf_viewer/widgets/utils/label.dart'; typedef MousePositionDrag = ({ Offset pos, double lastX, double lastY, - double zoom }); -typedef MouseListener = Widget Function( - ValueNotifier notifier); +typedef MouseEventData = ({ + MousePositionDrag mousePositionPrimary, + MousePositionDrag mousePositionSecondary, + double zoom, +}); + +typedef MouseListener = Widget Function(ValueNotifier notifier); -class MouseMovementNotifier extends StatelessWidget { +class MouseMovementNotifier extends StatefulWidget { const MouseMovementNotifier({super.key, required this.mouseListener}); final MouseListener mouseListener; @override - Widget build(BuildContext context) { - double lastX = 0; - double lastY = 0; - double refX = 0; - double refY = 0; + State createState() => _MouseMovementNotifierState(); +} + +class _MouseMovementNotifierState extends State { + (double, double) _lastXs = (0, 0); + (double, double) _lastYs = (0, 0); + double _refX = 0; + double _refY = 0; + bool _isPrimary = false; + + final MousePositionDrag defaultPos = + (pos: const Offset(0, 0), lastX: 0, lastY: 0); + late ValueNotifier mousePosNotifier; - final mousePosNotifier = ValueNotifier( - (pos: const Offset(0, 0), lastX: 0, lastY: 0, zoom: 1)); + @override + void initState() { + mousePosNotifier = ValueNotifier(( + mousePositionPrimary: defaultPos, + mousePositionSecondary: defaultPos, + zoom: 1.0, + )); + super.initState(); + } + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; return Listener( onPointerUp: (event) { + final currentLastX = _isPrimary ? _lastXs.$1 : _lastXs.$2; + final currentLastY = _isPrimary ? _lastYs.$1 : _lastYs.$2; + final MousePositionDrag newPosData = ( + pos: Offset(event.position.dx - _refX, event.position.dy - _refY), + lastX: currentLastX, + lastY: currentLastY, + ); mousePosNotifier.value = ( - pos: Offset(event.position.dx - refX, event.position.dy - refY), - lastX: lastX, - lastY: lastY, - zoom: mousePosNotifier.value.zoom + mousePositionPrimary: _isPrimary + ? newPosData + : mousePosNotifier.value.mousePositionPrimary, + mousePositionSecondary: _isPrimary + ? mousePosNotifier.value.mousePositionSecondary + : newPosData, + zoom: mousePosNotifier.value.zoom, ); - lastX = event.position.dx - refX + lastX; - lastY = event.position.dy - refY + lastY; + final newLastX = event.position.dx - _refX + currentLastX; + final newLastY = event.position.dy - _refY + currentLastY; + _lastXs = _isPrimary ? (newLastX, _lastXs.$2) : (_lastXs.$1, newLastX); + _lastYs = _isPrimary ? (newLastY, _lastYs.$2) : (_lastYs.$1, newLastY); }, onPointerDown: (event) { - if (event.buttons == kPrimaryMouseButton) { - refX = event.position.dx; - refY = event.position.dy; - mousePosNotifier.value = ( - pos: const Offset(0, 0), - lastX: lastX, - lastY: lastY, - zoom: mousePosNotifier.value.zoom - ); - } + _refX = event.position.dx; + _refY = event.position.dy; + _isPrimary = event.buttons == kPrimaryMouseButton; + final MousePositionDrag newPosData = ( + pos: const Offset(0, 0), + lastX: _isPrimary ? _lastXs.$1 : _lastXs.$2, + lastY: _isPrimary ? _lastYs.$1 : _lastYs.$2, + ); + mousePosNotifier.value = ( + mousePositionPrimary: _isPrimary + ? newPosData + : mousePosNotifier.value.mousePositionPrimary, + mousePositionSecondary: _isPrimary + ? mousePosNotifier.value.mousePositionSecondary + : newPosData, + zoom: mousePosNotifier.value.zoom, + ); }, onPointerMove: (event) { - if (event.buttons == kPrimaryMouseButton) { - mousePosNotifier.value = ( - pos: Offset(event.position.dx - refX, event.position.dy - refY), - lastX: lastX, - lastY: lastY, - zoom: mousePosNotifier.value.zoom - ); - } + final MousePositionDrag newPosData = ( + pos: Offset(event.position.dx - _refX, event.position.dy - _refY), + lastX: _isPrimary ? _lastXs.$1 : _lastXs.$2, + lastY: _isPrimary ? _lastYs.$1 : _lastYs.$2, + ); + mousePosNotifier.value = ( + mousePositionPrimary: _isPrimary + ? newPosData + : mousePosNotifier.value.mousePositionPrimary, + mousePositionSecondary: _isPrimary + ? mousePosNotifier.value.mousePositionSecondary + : newPosData, + zoom: mousePosNotifier.value.zoom, + ); }, onPointerSignal: (event) { if (event is PointerScrollEvent) { final delta = event.scrollDelta.dy / MediaQuery.of(context).size.height; mousePosNotifier.value = ( - pos: mousePosNotifier.value.pos, - lastX: mousePosNotifier.value.lastX, - lastY: mousePosNotifier.value.lastY, + mousePositionPrimary: mousePosNotifier.value.mousePositionPrimary, + mousePositionSecondary: + mousePosNotifier.value.mousePositionSecondary, zoom: delta < 0 ? max(mousePosNotifier.value.zoom + delta, 1) : mousePosNotifier.value.zoom + delta, ); } }, - child: mouseListener(mousePosNotifier), + child: Column( + children: [ + Expanded(child: widget.mouseListener(mousePosNotifier)), + Button.outlinedPrimary( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Label.regular( + "Reset transformations", + isBold: true, + color: colorScheme.onBackground, + ), + Icon( + Icons.refresh, + color: colorScheme.onBackground, + ) + ], + ), + onPressed: () { + _lastXs = (0, 0); + _lastYs = (0, 0); + _refX = 0; + _refY = 0; + mousePosNotifier.value = ( + mousePositionPrimary: defaultPos, + mousePositionSecondary: defaultPos, + zoom: 1.0, + ); + }, + ), + const Gap(8.0), + ], + ), ); } } diff --git a/lib/widgets/viewer/model_drawer.dart b/lib/widgets/viewer/model_drawer.dart index 75fb4d9..de21eb3 100644 --- a/lib/widgets/viewer/model_drawer.dart +++ b/lib/widgets/viewer/model_drawer.dart @@ -26,7 +26,7 @@ class ModelDrawer extends CustomPainter { required this.showSkeleton, }) : super(repaint: mousePosition); - final ValueNotifier mousePosition; + final ValueNotifier mousePosition; final Model model; final ui.Image? overridingTexture; final bool showNormals; @@ -156,17 +156,29 @@ class ModelDrawer extends CustomPainter { @override void paint(Canvas canvas, Size size) { + final rotationMouseData = mousePosition.value.mousePositionPrimary; final zRotationAngle = - (mousePosition.value.pos.dx + mousePosition.value.lastX) * + (rotationMouseData.pos.dx + rotationMouseData.lastX) * 2 * math.pi / size.width; final xRotationAngle = - (mousePosition.value.pos.dy + mousePosition.value.lastY) * + (rotationMouseData.pos.dy + rotationMouseData.lastY) * 2 * math.pi / size.height; + final boxMax = model.boundingBox.maxOfCoordinates; + final translationMouseData = mousePosition.value.mousePositionSecondary; + final xTranslation = + (translationMouseData.pos.dx + translationMouseData.lastX) / + size.width * + boxMax; + final zTranslation = + -(translationMouseData.pos.dy + translationMouseData.lastY) / + size.height * + boxMax; + transformation.setTranslation(xTranslation, zTranslation); transformation.setQuaternion(xRotationAngle, 0, zRotationAngle); transformation.scaleFactor = mousePosition.value.zoom; transformation.composeMatrix(); @@ -181,8 +193,15 @@ class ModelDrawer extends CustomPainter { showNormals: showNormals, showCloths: showCloth, showTexture: showTexture, - showSkeleton: showSkeleton, ); + if (showSkeleton) { + model.drawSkeleton( + transformation, + size, + canvas, + meshColor, + ); + } drawAxis(size, canvas); } diff --git a/lib/widgets/viewer/viewer.dart b/lib/widgets/viewer/viewer.dart index 58b53c1..a42e1f6 100644 --- a/lib/widgets/viewer/viewer.dart +++ b/lib/widgets/viewer/viewer.dart @@ -358,7 +358,7 @@ class Viewer extends StatelessWidget { } } -typedef viewerControlBuilder = Widget Function(bool showCloth, bool showNormals, +typedef ViewerControlBuilder = Widget Function(bool showCloth, bool showNormals, bool showTexture, bool showSkeleton, ChunkAttributes?); class ViewerControlWrapper extends ConsumerWidget { @@ -368,7 +368,7 @@ class ViewerControlWrapper extends ConsumerWidget { required this.overridingAttributes, }); - final viewerControlBuilder builder; + final ViewerControlBuilder builder; final ChunkAttributes? overridingAttributes; @override Widget build(BuildContext context, ref) {