From 87c96d06a4868de8816f9ab494568d5519c2e6d6 Mon Sep 17 00:00:00 2001 From: Nick Fisher Date: Thu, 26 Sep 2024 15:52:23 +0800 Subject: [PATCH] chore: rearrange library dirs, gesture handler improvements --- thermion_dart/lib/thermion_dart.dart | 1 + .../lib/thermion_dart/input/input.dart | 5 + .../input/src/delegate_gesture_handler.dart | 237 ++ .../thermion_dart/input/src/delegates.dart | 27 + ...fault_keyboard_camera_flight_delegate.dart | 87 + .../default_pan_camera_delegate.dart | 4 +- .../fixed_orbit_camera_rotation_delegate.dart | 131 + .../free_flight_camera_delegate.dart | 156 ++ .../picking_camera_gesture_handler.dart | 230 ++ .../input/src/input_handler.dart | 47 + .../viewer/ffi/src/camera_ffi.dart | 19 +- .../viewer/ffi/src/thermion_dart.g.dart | 6 + .../viewer/ffi/src/thermion_viewer_ffi.dart | 27 +- .../viewer/shared_types/camera.dart | 5 +- .../viewer/shared_types/manipulator.dart | 2 +- .../viewer/thermion_viewer_base.dart | 14 +- .../viewer/thermion_viewer_stub.dart | 34 +- .../viewer/web_js/src/thermion_viewer_js.dart | 34 +- .../viewer/web_wasm/src/camera.dart | 20 + .../web_wasm/src/thermion_viewer_wasm.dart | 30 +- thermion_dart/pubspec.yaml | 4 +- thermion_dart/test/{ => assets}/cube.glb | Bin thermion_dart/test/assets/cube_texture.svg | 181 ++ .../test/assets/cube_texture_256x256.png | Bin 0 -> 4236 bytes .../{ => assets}/cube_texture_512x512.png | Bin thermion_dart/test/camera_tests.dart | 130 + thermion_dart/test/helpers.dart | 30 + thermion_dart/test/input_handlers.dart | 119 + thermion_dart/test/input_handlers.mocks.dart | 2422 +++++++++++++++++ thermion_dart/test/integration_test.dart | 460 ++-- .../thermion_flutter/analysis_options.yaml | 3 + ...fault_keyboard_camera_flight_delegate.dart | 87 - .../src/gestures/default_pick_delegate.dart | 15 - .../gestures/default_velocity_delegate.dart | 46 - .../default_zoom_camera_delegate.dart | 47 - .../gestures/delegate_gesture_handler.dart | 245 -- .../lib/src/gestures/delegates.dart | 30 - .../fixed_orbit_camera_rotation_delegate.dart | 150 - .../gestures/free_flight_camera_delegate.dart | 206 -- .../picking_camera_gesture_handler.dart | 231 -- ...obile_gesture_handler_selector_widget.dart | 28 - .../thermion_gesture_detector_desktop.dart | 56 - .../gestures/thermion_gesture_handler.dart | 54 - .../gestures/thermion_listener_widget.dart | 132 - .../camera/camera_options_widget.dart | 2 +- .../camera/camera_orientation_widget.dart | 0 ...obile_gesture_handler_selector_widget.dart | 28 + .../gestures/thermion_gesture_detector.dart | 0 .../thermion_gesture_detector_desktop.dart | 60 + .../thermion_gesture_detector_mobile.dart | 22 +- .../gestures/thermion_listener_widget.dart | 165 ++ .../debug/child_renderable_widget.dart | 0 .../debug/skeleton_menu_item_widget.dart | 0 .../{ => src}/lights/ibl_rotation_slider.dart | 0 .../{ => src}/lights/light_slider.dart | 0 .../widgets/{ => src}/resize_observer.dart | 0 .../widgets/{ => src}/thermion_widget.dart | 7 +- .../{ => src}/thermion_widget_web.dart | 0 .../{ => src}/thermion_widget_web_impl.dart | 0 .../{ => src}/thermion_widget_web_stub.dart | 0 .../transparent_filament_widget.dart | 0 .../lib/src/widgets/widgets.dart | 4 + .../lib/thermion_flutter.dart | 7 +- 63 files changed, 4418 insertions(+), 1669 deletions(-) create mode 100644 thermion_dart/lib/thermion_dart/input/input.dart create mode 100644 thermion_dart/lib/thermion_dart/input/src/delegate_gesture_handler.dart create mode 100644 thermion_dart/lib/thermion_dart/input/src/delegates.dart create mode 100644 thermion_dart/lib/thermion_dart/input/src/implementations/default_keyboard_camera_flight_delegate.dart rename {thermion_flutter/thermion_flutter/lib/src/gestures => thermion_dart/lib/thermion_dart/input/src/implementations}/default_pan_camera_delegate.dart (91%) create mode 100644 thermion_dart/lib/thermion_dart/input/src/implementations/fixed_orbit_camera_rotation_delegate.dart create mode 100644 thermion_dart/lib/thermion_dart/input/src/implementations/free_flight_camera_delegate.dart create mode 100644 thermion_dart/lib/thermion_dart/input/src/implementations/picking_camera_gesture_handler.dart create mode 100644 thermion_dart/lib/thermion_dart/input/src/input_handler.dart rename thermion_dart/test/{ => assets}/cube.glb (100%) create mode 100644 thermion_dart/test/assets/cube_texture.svg create mode 100644 thermion_dart/test/assets/cube_texture_256x256.png rename thermion_dart/test/{ => assets}/cube_texture_512x512.png (100%) create mode 100644 thermion_dart/test/camera_tests.dart create mode 100644 thermion_dart/test/input_handlers.dart create mode 100644 thermion_dart/test/input_handlers.mocks.dart delete mode 100644 thermion_flutter/thermion_flutter/lib/src/gestures/default_keyboard_camera_flight_delegate.dart delete mode 100644 thermion_flutter/thermion_flutter/lib/src/gestures/default_pick_delegate.dart delete mode 100644 thermion_flutter/thermion_flutter/lib/src/gestures/default_velocity_delegate.dart delete mode 100644 thermion_flutter/thermion_flutter/lib/src/gestures/default_zoom_camera_delegate.dart delete mode 100644 thermion_flutter/thermion_flutter/lib/src/gestures/delegate_gesture_handler.dart delete mode 100644 thermion_flutter/thermion_flutter/lib/src/gestures/delegates.dart delete mode 100644 thermion_flutter/thermion_flutter/lib/src/gestures/fixed_orbit_camera_rotation_delegate.dart delete mode 100644 thermion_flutter/thermion_flutter/lib/src/gestures/free_flight_camera_delegate.dart delete mode 100644 thermion_flutter/thermion_flutter/lib/src/gestures/picking_camera_gesture_handler.dart delete mode 100644 thermion_flutter/thermion_flutter/lib/src/widgets/camera/gestures/mobile_gesture_handler_selector_widget.dart delete mode 100644 thermion_flutter/thermion_flutter/lib/src/widgets/camera/gestures/thermion_gesture_detector_desktop.dart delete mode 100644 thermion_flutter/thermion_flutter/lib/src/widgets/camera/gestures/thermion_gesture_handler.dart delete mode 100644 thermion_flutter/thermion_flutter/lib/src/widgets/camera/gestures/thermion_listener_widget.dart rename thermion_flutter/thermion_flutter/lib/src/widgets/{ => src}/camera/camera_options_widget.dart (99%) rename thermion_flutter/thermion_flutter/lib/src/widgets/{ => src}/camera/camera_orientation_widget.dart (100%) create mode 100644 thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/gestures/mobile_gesture_handler_selector_widget.dart rename thermion_flutter/thermion_flutter/lib/src/widgets/{ => src}/camera/gestures/thermion_gesture_detector.dart (100%) create mode 100644 thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/gestures/thermion_gesture_detector_desktop.dart rename thermion_flutter/thermion_flutter/lib/src/widgets/{ => src}/camera/gestures/thermion_gesture_detector_mobile.dart (92%) create mode 100644 thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/gestures/thermion_listener_widget.dart rename thermion_flutter/thermion_flutter/lib/src/widgets/{ => src}/debug/child_renderable_widget.dart (100%) rename thermion_flutter/thermion_flutter/lib/src/widgets/{ => src}/debug/skeleton_menu_item_widget.dart (100%) rename thermion_flutter/thermion_flutter/lib/src/widgets/{ => src}/lights/ibl_rotation_slider.dart (100%) rename thermion_flutter/thermion_flutter/lib/src/widgets/{ => src}/lights/light_slider.dart (100%) rename thermion_flutter/thermion_flutter/lib/src/widgets/{ => src}/resize_observer.dart (100%) rename thermion_flutter/thermion_flutter/lib/src/widgets/{ => src}/thermion_widget.dart (96%) rename thermion_flutter/thermion_flutter/lib/src/widgets/{ => src}/thermion_widget_web.dart (100%) rename thermion_flutter/thermion_flutter/lib/src/widgets/{ => src}/thermion_widget_web_impl.dart (100%) rename thermion_flutter/thermion_flutter/lib/src/widgets/{ => src}/thermion_widget_web_stub.dart (100%) rename thermion_flutter/thermion_flutter/lib/src/widgets/{ => src}/transparent_filament_widget.dart (100%) create mode 100644 thermion_flutter/thermion_flutter/lib/src/widgets/widgets.dart diff --git a/thermion_dart/lib/thermion_dart.dart b/thermion_dart/lib/thermion_dart.dart index b2b1d1e4..7ade6b33 100644 --- a/thermion_dart/lib/thermion_dart.dart +++ b/thermion_dart/lib/thermion_dart.dart @@ -1,4 +1,5 @@ library filament_dart; +export 'thermion_dart/input/input.dart'; export 'thermion_dart/thermion_viewer.dart'; export 'thermion_dart/utils/geometry.dart'; diff --git a/thermion_dart/lib/thermion_dart/input/input.dart b/thermion_dart/lib/thermion_dart/input/input.dart new file mode 100644 index 00000000..ed7a15b0 --- /dev/null +++ b/thermion_dart/lib/thermion_dart/input/input.dart @@ -0,0 +1,5 @@ +library; + +export 'src/input_handler.dart'; +export 'src/delegates.dart'; +export 'src/delegate_gesture_handler.dart'; diff --git a/thermion_dart/lib/thermion_dart/input/src/delegate_gesture_handler.dart b/thermion_dart/lib/thermion_dart/input/src/delegate_gesture_handler.dart new file mode 100644 index 00000000..616919bf --- /dev/null +++ b/thermion_dart/lib/thermion_dart/input/src/delegate_gesture_handler.dart @@ -0,0 +1,237 @@ +import 'dart:async'; +import 'package:logging/logging.dart'; +import 'package:thermion_dart/thermion_dart/input/src/delegates.dart'; +import 'package:vector_math/vector_math_64.dart'; + +import '../../viewer/thermion_viewer_base.dart'; +import 'implementations/fixed_orbit_camera_rotation_delegate.dart'; +import 'implementations/free_flight_camera_delegate.dart'; +import 'input_handler.dart'; + +class DelegateInputHandler implements InputHandler { + final ThermionViewer viewer; + + final _logger = Logger("DelegateInputHandler"); + + InputHandlerDelegate? transformDelegate; + PickDelegate? pickDelegate; + + final Set _pressedKeys = {}; + + final _inputDeltas = {}; + + Map _actions = { + InputType.LMB_HOLD_AND_MOVE: InputAction.TRANSLATE, + InputType.MMB_HOLD_AND_MOVE: InputAction.ROTATE, + InputType.SCROLLWHEEL: InputAction.TRANSLATE, + InputType.POINTER_MOVE: InputAction.NONE, + InputType.KEYDOWN_W: InputAction.TRANSLATE, + InputType.KEYDOWN_S: InputAction.TRANSLATE, + InputType.KEYDOWN_A: InputAction.TRANSLATE, + InputType.KEYDOWN_D: InputAction.TRANSLATE, + }; + + final _axes = {}; + + void setTransformForAction(InputType inputType, Matrix3 transform) { + _axes[inputType] = transform; + } + + DelegateInputHandler({ + required this.viewer, + required this.transformDelegate, + this.pickDelegate, + Map? actions, + }) { + if (actions != null) { + _actions = actions; + } + + for (var gestureType in InputType.values) { + _inputDeltas[gestureType] = Vector3.zero(); + } + + viewer.registerRequestFrameHook(process); + } + + factory DelegateInputHandler.fixedOrbit(ThermionViewer viewer, + {double minimumDistance = 10.0, + double? Function(Vector3)? getDistanceToTarget, + ThermionEntity? entity, + PickDelegate? pickDelegate}) => + DelegateInputHandler( + viewer: viewer, + pickDelegate: pickDelegate, + transformDelegate: FixedOrbitRotateInputHandlerDelegate(viewer, + getDistanceToTarget: getDistanceToTarget, + minimumDistance: minimumDistance), + actions: { + InputType.MMB_HOLD_AND_MOVE: InputAction.ROTATE, + InputType.SCROLLWHEEL: InputAction.TRANSLATE + }); + + factory DelegateInputHandler.flight(ThermionViewer viewer, + {PickDelegate? pickDelegate, bool freeLook=false}) => + DelegateInputHandler( + viewer: viewer, + pickDelegate: pickDelegate, + transformDelegate: FreeFlightInputHandlerDelegate(viewer), + actions: { + InputType.MMB_HOLD_AND_MOVE: InputAction.ROTATE, + InputType.SCROLLWHEEL: InputAction.TRANSLATE, + InputType.LMB_HOLD_AND_MOVE: InputAction.TRANSLATE, + InputType.KEYDOWN_A: InputAction.TRANSLATE, + InputType.KEYDOWN_W: InputAction.TRANSLATE, + InputType.KEYDOWN_S: InputAction.TRANSLATE, + InputType.KEYDOWN_D: InputAction.TRANSLATE, + if(freeLook) + InputType.POINTER_MOVE: InputAction.ROTATE, + }); + + bool _processing = false; + Future process() async { + _processing = true; + for (var gestureType in _inputDeltas.keys) { + var vector = _inputDeltas[gestureType]!; + var action = _actions[gestureType]; + if (action == null) { + continue; + } + final transform = _axes[gestureType]; + if (transform != null) { + vector = transform * vector; + } + + await transformDelegate?.queue(action, vector); + } + + for (final key in _pressedKeys) { + InputAction? keyAction; + Vector3? vector; + + switch (key) { + case PhysicalKey.W: + keyAction = _actions[InputType.KEYDOWN_W]; + vector = Vector3(0, 0, -1); + break; + case PhysicalKey.A: + keyAction = _actions[InputType.KEYDOWN_A]; + vector = Vector3(-1, 0, 0); + break; + case PhysicalKey.S: + keyAction = _actions[InputType.KEYDOWN_S]; + vector = Vector3(0, 0, 1); + break; + case PhysicalKey.D: + keyAction = _actions[InputType.KEYDOWN_D]; + vector = Vector3(1, 0, 0); + break; + } + if (keyAction != null) { + var transform = _axes[keyAction]; + if (transform != null) { + vector = transform * vector; + } + transformDelegate?.queue(keyAction, vector!); + } + } + + await transformDelegate?.execute(); + _inputDeltas.clear(); + _processing = false; + } + + @override + Future onPointerDown(Vector2 localPosition, bool isMiddle) async { + if (!isMiddle) { + final action = _actions[InputType.LMB_DOWN]; + switch (action) { + case InputAction.PICK: + pickDelegate?.pick(localPosition); + default: + // noop + } + } + } + + @override + Future onPointerMove( + Vector2 localPosition, Vector2 delta, bool isMiddle) async { + if (_processing) { + return; + } + if (isMiddle) { + _inputDeltas[InputType.MMB_HOLD_AND_MOVE] = + (_inputDeltas[InputType.MMB_HOLD_AND_MOVE] ?? Vector3.zero()) + Vector3(delta.x, delta.y, 0.0); + } else { + _inputDeltas[InputType.LMB_HOLD_AND_MOVE] = + (_inputDeltas[InputType.LMB_HOLD_AND_MOVE] ?? Vector3.zero()) + Vector3(delta.x, delta.y, 0.0); + } + // else { + // _inputDeltas[InputType.POINTER_MOVE] = + // (_inputDeltas[InputType.POINTER_MOVE] ?? Vector3.zero()) + delta; + // } + } + + @override + Future onPointerUp(bool isMiddle) async {} + + @override + Future onPointerHover(Vector2 localPosition, Vector2 delta) async { + if (_processing) { + return; + } + _inputDeltas[InputType.POINTER_MOVE] = + (_inputDeltas[InputType.POINTER_MOVE] ?? Vector3.zero()) + Vector3(delta.x, delta.y, 0.0); + } + + @override + Future onPointerScroll( + Vector2 localPosition, double scrollDelta) async { + if (_processing) { + return; + } + try { + _inputDeltas[InputType.SCROLLWHEEL] = + (_inputDeltas[InputType.SCROLLWHEEL] ?? Vector3.zero()) + + Vector3(0,0, scrollDelta > 0 ? 1 : -1); + } catch (e) { + _logger.warning("Error during scroll accumulation: $e"); + } + } + + @override + Future dispose() async { + viewer.unregisterRequestFrameHook(process); + } + + @override + Future get initialized => viewer.initialized; + + @override + Future onScaleEnd() async {} + + @override + Future onScaleStart() async {} + + @override + Future onScaleUpdate() async {} + + @override + void setActionForType(InputType gestureType, InputAction gestureAction) { + _actions[gestureType] = gestureAction; + } + + @override + InputAction? getActionForType(InputType gestureType) { + return _actions[gestureType]; + } + + void keyDown(PhysicalKey key) { + _pressedKeys.add(key); + } + + void keyUp(PhysicalKey key) { + _pressedKeys.remove(key); + } +} diff --git a/thermion_dart/lib/thermion_dart/input/src/delegates.dart b/thermion_dart/lib/thermion_dart/input/src/delegates.dart new file mode 100644 index 00000000..c2cdb2f1 --- /dev/null +++ b/thermion_dart/lib/thermion_dart/input/src/delegates.dart @@ -0,0 +1,27 @@ +import 'package:vector_math/vector_math_64.dart'; + +import 'input_handler.dart'; + +abstract class InputHandlerDelegate { + Future queue(InputAction action, Vector3? delta); + Future execute(); +} + +abstract class VelocityDelegate { + Vector2? get velocity; + + void updateVelocity(Vector2 delta); + + void startDeceleration(); + + void stopDeceleration(); + + void dispose() { + stopDeceleration(); + } +} + +abstract class PickDelegate { + const PickDelegate(); + void pick(Vector2 location); +} diff --git a/thermion_dart/lib/thermion_dart/input/src/implementations/default_keyboard_camera_flight_delegate.dart b/thermion_dart/lib/thermion_dart/input/src/implementations/default_keyboard_camera_flight_delegate.dart new file mode 100644 index 00000000..eb95816d --- /dev/null +++ b/thermion_dart/lib/thermion_dart/input/src/implementations/default_keyboard_camera_flight_delegate.dart @@ -0,0 +1,87 @@ +// import 'dart:async'; +// import 'dart:ui'; + +// import 'package:flutter/services.dart'; +// import 'package:thermion_dart/thermion_dart/thermion_viewer.dart'; +// import 'package:thermion_dart/thermion_dart/input/delegates.dart'; +// import 'package:vector_math/vector_math_64.dart'; + +// class DefaultKeyboardCameraFlightDelegate +// { +// final ThermionViewer viewer; + +// static const double _panSensitivity = 0.005; +// static const double _keyMoveSensitivity = 0.1; + +// final Map _pressedKeys = {}; +// Timer? _moveTimer; + +// DefaultKeyboardCameraFlightDelegate(this.viewer) { +// _startMoveLoop(); +// } + +// @override +// Future panCamera(Offset delta, Vector2? velocity) async { +// double deltaX = delta.dx; +// double deltaY = delta.dy; +// deltaX *= _panSensitivity * viewer.pixelRatio; +// deltaY *= _panSensitivity * viewer.pixelRatio; + +// await _moveCamera(deltaX, deltaY, 0); +// } + +// @override +// Future onKeypress(PhysicalKeyboardKey key) async { +// _pressedKeys[key] = true; +// } + +// // New method to handle key release +// Future onKeyRelease(PhysicalKeyboardKey key) async { +// _pressedKeys.remove(key); +// } + +// void _startMoveLoop() { +// _moveTimer = Timer.periodic( +// Duration(milliseconds: 16), (_) => _processKeyboardInput()); +// } + +// Future _processKeyboardInput() async { +// double dx = 0, dy = 0, dz = 0; + +// if (_pressedKeys[PhysicalKeyboardKey.keyW] == true) +// dz += _keyMoveSensitivity; +// if (_pressedKeys[PhysicalKeyboardKey.keyS] == true) +// dz -= _keyMoveSensitivity; +// if (_pressedKeys[PhysicalKeyboardKey.keyA] == true) +// dx -= _keyMoveSensitivity; +// if (_pressedKeys[PhysicalKeyboardKey.keyD] == true) +// dx += _keyMoveSensitivity; + +// if (dx != 0 || dy != 0 || dz != 0) { +// await _moveCamera(dx, dy, dz); +// } +// // Removed _pressedKeys.clear(); from here +// } + +// Future _moveCamera(double dx, double dy, double dz) async { +// Matrix4 currentModelMatrix = await viewer.getCameraModelMatrix(); +// Vector3 currentPosition = currentModelMatrix.getTranslation(); +// Quaternion currentRotation = +// Quaternion.fromRotation(currentModelMatrix.getRotation()); + +// Vector3 forward = Vector3(0, 0, -1)..applyQuaternion(currentRotation); +// Vector3 right = Vector3(1, 0, 0)..applyQuaternion(currentRotation); +// Vector3 up = Vector3(0, 1, 0)..applyQuaternion(currentRotation); + +// Vector3 moveOffset = right * dx + up * dy + forward * dz; +// Vector3 newPosition = currentPosition + moveOffset; + +// Matrix4 newModelMatrix = +// Matrix4.compose(newPosition, currentRotation, Vector3(1, 1, 1)); +// await viewer.setCameraModelMatrix4(newModelMatrix); +// } + +// void dispose() { +// _moveTimer?.cancel(); +// } +// } \ No newline at end of file diff --git a/thermion_flutter/thermion_flutter/lib/src/gestures/default_pan_camera_delegate.dart b/thermion_dart/lib/thermion_dart/input/src/implementations/default_pan_camera_delegate.dart similarity index 91% rename from thermion_flutter/thermion_flutter/lib/src/gestures/default_pan_camera_delegate.dart rename to thermion_dart/lib/thermion_dart/input/src/implementations/default_pan_camera_delegate.dart index a9d05751..288627fd 100644 --- a/thermion_flutter/thermion_flutter/lib/src/gestures/default_pan_camera_delegate.dart +++ b/thermion_dart/lib/thermion_dart/input/src/implementations/default_pan_camera_delegate.dart @@ -4,12 +4,12 @@ // import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/delegates.dart'; // import 'package:vector_math/vector_math_64.dart'; -// class DefaultPanCameraDelegate implements PanCameraDelegate { +// class DefaultPanInputHandlerDelegate implements PanInputHandlerDelegate { // final ThermionViewer viewer; // static const double _panSensitivity = 0.005; -// DefaultPanCameraDelegate(this.viewer); +// DefaultPanInputHandlerDelegate(this.viewer); // static const double _panSensitivity = 0.005; // @override diff --git a/thermion_dart/lib/thermion_dart/input/src/implementations/fixed_orbit_camera_rotation_delegate.dart b/thermion_dart/lib/thermion_dart/input/src/implementations/fixed_orbit_camera_rotation_delegate.dart new file mode 100644 index 00000000..f44dd5aa --- /dev/null +++ b/thermion_dart/lib/thermion_dart/input/src/implementations/fixed_orbit_camera_rotation_delegate.dart @@ -0,0 +1,131 @@ +import 'dart:async'; +import 'package:thermion_dart/thermion_dart/thermion_viewer.dart'; +import 'package:thermion_dart/thermion_dart/input/src/delegates.dart'; +import 'package:vector_math/vector_math_64.dart'; +import '../../../viewer/shared_types/camera.dart'; +import '../input_handler.dart'; + +class FixedOrbitRotateInputHandlerDelegate implements InputHandlerDelegate { + final ThermionViewer viewer; + late Future _camera; + final double minimumDistance; + double? Function(Vector3)? getDistanceToTarget; + + Vector2 _queuedRotationDelta = Vector2.zero(); + double _queuedZoomDelta = 0.0; + + static final _up = Vector3(0, 1, 0); + Timer? _updateTimer; + + FixedOrbitRotateInputHandlerDelegate( + this.viewer, { + this.getDistanceToTarget, + this.minimumDistance = 10.0, + }) { + _camera = viewer.getMainCamera(); + } + + void dispose() { + _updateTimer?.cancel(); + } + + @override + Future queue(InputAction action, Vector3? delta) async { + if (delta == null) return; + + switch (action) { + case InputAction.ROTATE: + _queuedRotationDelta += Vector2(delta.x, delta.y); + break; + case InputAction.TRANSLATE: + _queuedZoomDelta += delta!.z; + break; + case InputAction.PICK: + // Assuming PICK is used for zoom in this context + _queuedZoomDelta += delta.z; + break; + case InputAction.NONE: + // Do nothing + break; + } + } + + @override + Future execute() async { + if (_queuedRotationDelta.length2 == 0.0 && _queuedZoomDelta == 0.0) { + return; + } + + var viewMatrix = await viewer.getCameraViewMatrix(); + var modelMatrix = await viewer.getCameraModelMatrix(); + var projectionMatrix = await viewer.getCameraProjectionMatrix(); + var inverseProjectionMatrix = projectionMatrix.clone()..invert(); + Vector3 currentPosition = modelMatrix.getTranslation(); + + Vector3 forward = -currentPosition.normalized(); + Vector3 right = _up.cross(forward).normalized(); + Vector3 up = forward.cross(right); + + // Calculate intersection point and depth + double radius = getDistanceToTarget?.call(currentPosition) ?? 1.0; + if (radius != 1.0) { + radius = currentPosition.length - radius; + } + Vector3 intersection = (-forward).scaled(radius); + + final intersectionInViewSpace = viewMatrix * + Vector4(intersection.x, intersection.y, intersection.z, 1.0); + final intersectionInClipSpace = projectionMatrix * intersectionInViewSpace; + final intersectionInNdcSpace = + intersectionInClipSpace / intersectionInClipSpace.w; + + // Calculate new camera position based on rotation + final ndcX = 2 * + ((-_queuedRotationDelta.x * viewer.pixelRatio) / + viewer.viewportDimensions.$1); + final ndcY = 2 * + ((_queuedRotationDelta.y * viewer.pixelRatio) / + viewer.viewportDimensions.$2); + final ndc = Vector4(ndcX, ndcY, intersectionInNdcSpace.z, 1.0); + + var clipSpace = Vector4( + ndc.x * intersectionInClipSpace.w, + ndcY * intersectionInClipSpace.w, + ndc.z * intersectionInClipSpace.w, + intersectionInClipSpace.w); + Vector4 cameraSpace = inverseProjectionMatrix * clipSpace; + Vector4 worldSpace = modelMatrix * cameraSpace; + + var worldSpace3 = worldSpace.xyz.normalized() * currentPosition.length; + currentPosition = worldSpace3; + + // Apply zoom + if (_queuedZoomDelta != 0.0) { + Vector3 toSurface = currentPosition - intersection; + currentPosition = + currentPosition + toSurface.scaled(_queuedZoomDelta * 0.1); + } + + // Ensure minimum distance + if (currentPosition.length < radius + minimumDistance) { + currentPosition = + (currentPosition.normalized() * (radius + minimumDistance)); + } + + // Calculate view matrix + forward = -currentPosition.normalized(); + right = _up.cross(forward).normalized(); + up = forward.cross(right); + + Matrix4 newViewMatrix = makeViewMatrix(currentPosition, Vector3.zero(), up); + newViewMatrix.invert(); + + // Set the camera model matrix + var camera = await _camera; + await camera.setModelMatrix(newViewMatrix); + + // Reset queued deltas + _queuedRotationDelta = Vector2.zero(); + _queuedZoomDelta = 0.0; + } +} diff --git a/thermion_dart/lib/thermion_dart/input/src/implementations/free_flight_camera_delegate.dart b/thermion_dart/lib/thermion_dart/input/src/implementations/free_flight_camera_delegate.dart new file mode 100644 index 00000000..4b19fbec --- /dev/null +++ b/thermion_dart/lib/thermion_dart/input/src/implementations/free_flight_camera_delegate.dart @@ -0,0 +1,156 @@ +import 'dart:async'; +import 'package:thermion_dart/thermion_dart/input/src/input_handler.dart'; +import 'package:thermion_dart/thermion_dart/thermion_viewer.dart'; +import 'package:vector_math/vector_math_64.dart'; +import '../delegates.dart'; + +class FreeFlightInputHandlerDelegate implements InputHandlerDelegate { + + final ThermionViewer viewer; + final Vector3? minBounds; + final Vector3? maxBounds; + final double rotationSensitivity; + final double movementSensitivity; + final double zoomSensitivity; + final double panSensitivity; + + static final _up = Vector3(0, 1, 0); + static final _forward = Vector3(0, 0, -1); + static final Vector3 _right = Vector3(1, 0, 0); + + Vector2 _queuedRotationDelta = Vector2.zero(); + Vector2 _queuedPanDelta = Vector2.zero(); + double _queuedZoomDelta = 0.0; + Vector3 _queuedMoveDelta = Vector3.zero(); + + FreeFlightInputHandlerDelegate( + this.viewer, { + this.minBounds, + this.maxBounds, + this.rotationSensitivity = 0.001, + this.movementSensitivity = 0.1, + this.zoomSensitivity = 0.1, + this.panSensitivity = 0.1, + }); + + @override + Future queue(InputAction action, Vector3? delta) async { + if (delta == null) return; + + switch (action) { + case InputAction.ROTATE: + _queuedRotationDelta += Vector2(delta.x, delta.y); + break; + case InputAction.TRANSLATE: + _queuedPanDelta += Vector2(delta.x, delta.y); + _queuedZoomDelta += delta.z; + break; + case InputAction.PICK: + // Assuming PICK is used for zoom in this context + _queuedZoomDelta += delta.z; + break; + case InputAction.NONE: + // Do nothing + break; + } + } + + bool _executing = false; + + @override + Future execute() async { + if (_executing) { + return; + } + + _executing = true; + + if (_queuedRotationDelta.length2 == 0.0 && + _queuedPanDelta.length2 == 0.0 && + _queuedZoomDelta == 0.0 && + _queuedMoveDelta.length2 == 0.0) { + _executing = false; + return; + } + + Matrix4 currentModelMatrix = await viewer.getCameraModelMatrix(); + Vector3 currentPosition = currentModelMatrix.getTranslation(); + Quaternion currentRotation = + Quaternion.fromRotation(currentModelMatrix.getRotation()); + + // Apply rotation + if (_queuedRotationDelta.length2 > 0.0) { + double deltaX = + _queuedRotationDelta.x * rotationSensitivity * viewer.pixelRatio; + double deltaY = + _queuedRotationDelta.y * rotationSensitivity * viewer.pixelRatio; + + Quaternion yawRotation = Quaternion.axisAngle(_up, -deltaX); + Quaternion pitchRotation = Quaternion.axisAngle(_right, -deltaY); + + currentRotation = currentRotation * pitchRotation * yawRotation; + currentRotation.normalize(); + + _queuedRotationDelta = Vector2.zero(); + } + + // Apply pan + if (_queuedPanDelta.length2 > 0.0) { + Vector3 right = _right.clone()..applyQuaternion(currentRotation); + Vector3 up = _up.clone()..applyQuaternion(currentRotation); + + double deltaX = _queuedPanDelta.x * panSensitivity * viewer.pixelRatio; + double deltaY = _queuedPanDelta.y * panSensitivity * viewer.pixelRatio; + + Vector3 panOffset = right * deltaX + up * deltaY; + currentPosition += panOffset; + + _queuedPanDelta = Vector2.zero(); + } + + // Apply zoom + if (_queuedZoomDelta != 0.0) { + Vector3 forward = _forward.clone()..applyQuaternion(currentRotation); + currentPosition += forward * -_queuedZoomDelta * zoomSensitivity; + _queuedZoomDelta = 0.0; + } + + // Apply queued movement + if (_queuedMoveDelta.length2 > 0.0) { + Vector3 forward = _forward.clone()..applyQuaternion(currentRotation); + Vector3 right = _right.clone()..applyQuaternion(currentRotation); + Vector3 up = _up.clone()..applyQuaternion(currentRotation); + + Vector3 moveOffset = right * _queuedMoveDelta.x + + up * _queuedMoveDelta.y + + forward * _queuedMoveDelta.z; + currentPosition += moveOffset; + + _queuedMoveDelta = Vector3.zero(); + } + + // Constrain position + currentPosition = _constrainPosition(currentPosition); + + // Update camera + Matrix4 newModelMatrix = + Matrix4.compose(currentPosition, currentRotation, Vector3(1, 1, 1)); + await viewer.setCameraModelMatrix4(newModelMatrix); + + _executing = false; + } + + Vector3 _constrainPosition(Vector3 position) { + if (minBounds != null) { + position.x = position.x.clamp(minBounds!.x, double.infinity); + position.y = position.y.clamp(minBounds!.y, double.infinity); + position.z = position.z.clamp(minBounds!.z, double.infinity); + } + if (maxBounds != null) { + position.x = position.x.clamp(double.negativeInfinity, maxBounds!.x); + position.y = position.y.clamp(double.negativeInfinity, maxBounds!.y); + position.z = position.z.clamp(double.negativeInfinity, maxBounds!.z); + } + return position; + } +} diff --git a/thermion_dart/lib/thermion_dart/input/src/implementations/picking_camera_gesture_handler.dart b/thermion_dart/lib/thermion_dart/input/src/implementations/picking_camera_gesture_handler.dart new file mode 100644 index 00000000..f3056f20 --- /dev/null +++ b/thermion_dart/lib/thermion_dart/input/src/implementations/picking_camera_gesture_handler.dart @@ -0,0 +1,230 @@ +// import 'dart:async'; + +// import 'package:flutter/gestures.dart'; +// import 'package:flutter/services.dart'; +// import 'package:logging/logging.dart'; +// import 'package:thermion_dart/thermion_dart/entities/abstract_gizmo.dart'; +// import 'package:thermion_dart/thermion_dart/thermion_viewer.dart'; +// import 'dart:ui'; +// import 'package:thermion_dart/thermion_dart/input/input_handler.dart'; + +// // Renamed implementation +// class PickingCameraGestureHandler implements InputHandler { +// final ThermionViewer viewer; +// final bool enableCamera; +// final bool enablePicking; +// final Logger _logger = Logger("PickingCameraGestureHandler"); + +// AbstractGizmo? _gizmo; +// Timer? _scrollTimer; +// ThermionEntity? _highlightedEntity; +// StreamSubscription? _pickResultSubscription; + +// bool _gizmoAttached = false; + +// PickingCameraGestureHandler({ +// required this.viewer, +// this.enableCamera = true, +// this.enablePicking = true, +// }) { +// try { +// _gizmo = viewer.gizmo; +// } catch (err) { +// _logger.warning( +// "Failed to get gizmo. If you are running on WASM, this is expected"); +// } + +// _pickResultSubscription = viewer.pickResult.listen(_onPickResult); + +// // Add keyboard listener +// RawKeyboard.instance.addListener(_handleKeyEvent); +// } + +// @override +// ThermionGestureState get currentState => _currentState; + +// void _handleKeyEvent(RawKeyEvent event) { +// if (event is RawKeyDownEvent && +// event.logicalKey == LogicalKeyboardKey.escape) { +// _resetToNullState(); +// } +// } + +// void _resetToNullState() async { +// _currentState = ThermionGestureState.NULL; +// if (_highlightedEntity != null) { +// await viewer.removeStencilHighlight(_highlightedEntity!); +// _highlightedEntity = null; +// } +// } + +// void _onPickResult(FilamentPickResult result) async { +// var targetEntity = await viewer.getAncestor(result.entity) ?? result.entity; + +// if (_highlightedEntity != targetEntity) { +// if (_highlightedEntity != null) { +// await viewer.removeStencilHighlight(_highlightedEntity!); +// } + +// _highlightedEntity = targetEntity; +// if (_highlightedEntity != null) { +// await viewer.setStencilHighlight(_highlightedEntity!); +// _gizmo?.attach(_highlightedEntity!); +// } +// } +// } + +// @override +// Future onPointerHover(Offset localPosition, Offset delta) async { +// if (_gizmoAttached) { +// _gizmo?.checkHover(localPosition.dx, localPosition.dy); +// } + +// if (_highlightedEntity != null) { +// await viewer.queuePositionUpdateFromViewportCoords( +// _highlightedEntity!, +// localPosition.dx, +// localPosition.dy, +// ); +// } +// } + +// @override +// Future onPointerScroll(Offset localPosition, double scrollDelta) async { +// if (!enableCamera) { +// return; +// } +// if (_currentState == ThermionGestureState.NULL || +// _currentState == ThermionGestureState.ZOOMING) { +// await _zoom(localPosition, scrollDelta); +// } +// } + +// @override +// Future onPointerDown(Offset localPosition, int buttons) async { +// if (_highlightedEntity != null) { +// _resetToNullState(); +// return; +// } + +// if (enablePicking && buttons != kMiddleMouseButton) { +// viewer.pick(localPosition.dx.toInt(), localPosition.dy.toInt()); +// } + +// if (buttons == kMiddleMouseButton && enableCamera) { +// await viewer.rotateStart(localPosition.dx, localPosition.dy); +// _currentState = ThermionGestureState.ROTATING; +// } else if (buttons == kPrimaryMouseButton && enableCamera) { +// await viewer.panStart(localPosition.dx, localPosition.dy); +// _currentState = ThermionGestureState.PANNING; +// } +// } + +// @override +// Future onPointerMove( +// Offset localPosition, Offset delta, int buttons) async { +// if (_highlightedEntity != null) { +// await _handleEntityHighlightedMove(localPosition); +// return; +// } + +// switch (_currentState) { +// case ThermionGestureState.NULL: +// break; + +// case ThermionGestureState.ROTATING: +// if (enableCamera) { +// await viewer.rotateUpdate(localPosition.dx, localPosition.dy); +// } +// break; +// case ThermionGestureState.PANNING: +// if (enableCamera) { +// await viewer.panUpdate(localPosition.dx, localPosition.dy); +// } +// break; +// case ThermionGestureState.ZOOMING: +// // ignore +// break; +// } +// } + +// @override +// Future onPointerUp(int buttons) async { +// switch (_currentState) { +// case ThermionGestureState.ROTATING: +// await viewer.rotateEnd(); +// _currentState = ThermionGestureState.NULL; +// break; +// case ThermionGestureState.PANNING: +// await viewer.panEnd(); +// _currentState = ThermionGestureState.NULL; +// break; +// default: +// break; +// } +// } + +// Future _handleEntityHighlightedMove(Offset localPosition) async { +// if (_highlightedEntity != null) { +// await viewer.queuePositionUpdateFromViewportCoords( +// _highlightedEntity!, +// localPosition.dx, +// localPosition.dy, +// ); +// } +// } + +// Future _zoom(Offset localPosition, double scrollDelta) async { +// _scrollTimer?.cancel(); +// _currentState = ThermionGestureState.ZOOMING; +// await viewer.zoomBegin(); +// await viewer.zoomUpdate( +// localPosition.dx, localPosition.dy, scrollDelta > 0 ? 1 : -1); + +// _scrollTimer = Timer(const Duration(milliseconds: 100), () async { +// await viewer.zoomEnd(); +// _currentState = ThermionGestureState.NULL; +// }); +// } + +// @override +// void dispose() { +// _pickResultSubscription?.cancel(); +// if (_highlightedEntity != null) { +// viewer.removeStencilHighlight(_highlightedEntity!); +// } +// RawKeyboard.instance.removeListener(_handleKeyEvent); +// } + +// @override +// Future get initialized => viewer.initialized; + +// @override +// InputAction getActionForType(InputType type) { +// // TODO: implement getActionForType +// throw UnimplementedError(); +// } + +// @override +// Future onScaleEnd() { +// // TODO: implement onScaleEnd +// throw UnimplementedError(); +// } + +// @override +// Future onScaleStart() { +// // TODO: implement onScaleStart +// throw UnimplementedError(); +// } + +// @override +// Future onScaleUpdate() { +// // TODO: implement onScaleUpdate +// throw UnimplementedError(); +// } + +// @override +// void setActionForType(InputType type, InputAction action) { +// // TODO: implement setActionForType +// } +// } diff --git a/thermion_dart/lib/thermion_dart/input/src/input_handler.dart b/thermion_dart/lib/thermion_dart/input/src/input_handler.dart new file mode 100644 index 00000000..44096647 --- /dev/null +++ b/thermion_dart/lib/thermion_dart/input/src/input_handler.dart @@ -0,0 +1,47 @@ +import 'dart:async'; + +import 'package:vector_math/vector_math_64.dart'; + +enum InputType { + LMB_DOWN, + LMB_HOLD_AND_MOVE, + LMB_UP, + LMB_HOVER, + MMB_DOWN, + MMB_HOLD_AND_MOVE, + + MMB_UP, + MMB_HOVER, + SCALE1, + SCALE2, + SCROLLWHEEL, + POINTER_MOVE, + KEYDOWN_W, + KEYDOWN_A, + KEYDOWN_S, + KEYDOWN_D, +} + +enum PhysicalKey { W, A, S, D } + +enum InputAction { TRANSLATE, ROTATE, PICK, NONE } + +abstract class InputHandler { + + Future onPointerHover(Vector2 localPosition, Vector2 delta); + Future onPointerScroll(Vector2 localPosition, double scrollDelta); + Future onPointerDown(Vector2 localPosition, bool isMiddle); + Future onPointerMove(Vector2 localPosition, Vector2 delta, bool isMiddle); + Future onPointerUp(bool isMiddle); + Future onScaleStart(); + Future onScaleUpdate(); + Future onScaleEnd(); + Future get initialized; + Future dispose(); + + void setActionForType(InputType gestureType, InputAction gestureAction); + InputAction? getActionForType(InputType gestureType); + + void keyDown(PhysicalKey key); + void keyUp(PhysicalKey key); +} diff --git a/thermion_dart/lib/thermion_dart/viewer/ffi/src/camera_ffi.dart b/thermion_dart/lib/thermion_dart/viewer/ffi/src/camera_ffi.dart index b6704476..1e4c4068 100644 --- a/thermion_dart/lib/thermion_dart/viewer/ffi/src/camera_ffi.dart +++ b/thermion_dart/lib/thermion_dart/viewer/ffi/src/camera_ffi.dart @@ -29,9 +29,24 @@ class ThermionFFICamera extends Camera { var entity = Camera_getEntity(camera); Engine_setTransform(engine, entity, matrix4ToDouble4x4(transform)); } - + @override - Future setLensProjection({double near = kNear, double far = kFar, double aspect = 1.0, double focalLength = kFocalLength}) async { + Future setLensProjection( + {double near = kNear, + double far = kFar, + double aspect = 1.0, + double focalLength = kFocalLength}) async { Camera_setLensProjection(camera, near, far, aspect, focalLength); } + + @override + ThermionEntity getEntity() { + // TODO: implement getEntity + throw UnimplementedError(); + } + + @override + Future setModelMatrix(Matrix4 matrix) async { + Camera_setModelMatrix(camera, matrix4ToDouble4x4(matrix)); + } } diff --git a/thermion_dart/lib/thermion_dart/viewer/ffi/src/thermion_dart.g.dart b/thermion_dart/lib/thermion_dart/viewer/ffi/src/thermion_dart.g.dart index d9b665d4..e7b30929 100644 --- a/thermion_dart/lib/thermion_dart/viewer/ffi/src/thermion_dart.g.dart +++ b/thermion_dart/lib/thermion_dart/viewer/ffi/src/thermion_dart.g.dart @@ -915,6 +915,12 @@ external void Camera_setCustomProjectionWithCulling( double far, ); +@ffi.Native, double4x4)>(isLeaf: true) +external void Camera_setModelMatrix( + ffi.Pointer camera, + double4x4 modelMatrix, +); + @ffi.Native< ffi.Void Function(ffi.Pointer, ffi.Double, ffi.Double, ffi.Double, ffi.Double)>(isLeaf: true) diff --git a/thermion_dart/lib/thermion_dart/viewer/ffi/src/thermion_viewer_ffi.dart b/thermion_dart/lib/thermion_dart/viewer/ffi/src/thermion_viewer_ffi.dart index 70d97e32..91b8e542 100644 --- a/thermion_dart/lib/thermion_dart/viewer/ffi/src/thermion_viewer_ffi.dart +++ b/thermion_dart/lib/thermion_dart/viewer/ffi/src/thermion_viewer_ffi.dart @@ -119,8 +119,7 @@ class ThermionViewerFFI extends ThermionViewer { if (focalLength.abs() < 0.1) { focalLength = kFocalLength; } - Camera_setLensProjection( - mainCamera.camera, near, far, aspect, focalLength); + Camera_setLensProjection(mainCamera.camera, near, far, aspect, focalLength); } Future createSwapChain(double width, double height, @@ -1333,8 +1332,7 @@ class ThermionViewerFFI extends ThermionViewer { Future setCameraExposure( double aperture, double shutterSpeed, double sensitivity) async { var mainCamera = await getMainCamera() as ThermionFFICamera; - set_camera_exposure( - mainCamera.camera, aperture, shutterSpeed, sensitivity); + set_camera_exposure(mainCamera.camera, aperture, shutterSpeed, sensitivity); } /// @@ -2167,7 +2165,10 @@ class ThermionViewerFFI extends ThermionViewer { } @override - void requestFrame() { + Future requestFrame() async { + for (final hook in _hooks) { + await hook.call(); + } request_frame_render_thread(_viewer!); } @@ -2187,6 +2188,22 @@ class ThermionViewerFFI extends ThermionViewer { Future setActiveCamera(ThermionFFICamera camera) async { SceneManager_setCamera(_sceneManager!, camera.camera); } + + final _hooks = []; + + @override + Future registerRequestFrameHook(Future Function() hook) async { + if (!_hooks.contains(hook)) { + _hooks.add(hook); + } + } + + @override + Future unregisterRequestFrameHook(Future Function() hook) async { + if (_hooks.contains(hook)) { + _hooks.remove(hook); + } + } } class ThermionFFITexture extends ThermionTexture { diff --git a/thermion_dart/lib/thermion_dart/viewer/shared_types/camera.dart b/thermion_dart/lib/thermion_dart/viewer/shared_types/camera.dart index fe0dad08..e542d348 100644 --- a/thermion_dart/lib/thermion_dart/viewer/shared_types/camera.dart +++ b/thermion_dart/lib/thermion_dart/viewer/shared_types/camera.dart @@ -9,10 +9,13 @@ abstract class Camera { Future setLensProjection( {double near = kNear, double far = kFar, - double aspect=1.0, + double aspect = 1.0, double focalLength = kFocalLength}); Future getModelMatrix(); + Future setModelMatrix(Matrix4 matrix); + + ThermionEntity getEntity(); Future setTransform(Matrix4 transform); } diff --git a/thermion_dart/lib/thermion_dart/viewer/shared_types/manipulator.dart b/thermion_dart/lib/thermion_dart/viewer/shared_types/manipulator.dart index 21350980..7615e6a4 100644 --- a/thermion_dart/lib/thermion_dart/viewer/shared_types/manipulator.dart +++ b/thermion_dart/lib/thermion_dart/viewer/shared_types/manipulator.dart @@ -1,4 +1,4 @@ // see filament Manipulator.h for more details @Deprecated( - "This is used the native pointer manipulator Prefer ThermionGestureHandler instead") + "This is used the native pointer manipulator Prefer InputHandler instead") enum ManipulatorMode { ORBIT, MAP, FREE_FLIGHT } diff --git a/thermion_dart/lib/thermion_dart/viewer/thermion_viewer_base.dart b/thermion_dart/lib/thermion_dart/viewer/thermion_viewer_base.dart index b7fa875b..a53f96dd 100644 --- a/thermion_dart/lib/thermion_dart/viewer/thermion_viewer_base.dart +++ b/thermion_dart/lib/thermion_dart/viewer/thermion_viewer_base.dart @@ -66,7 +66,7 @@ abstract class ThermionViewer { /// /// Requests a single frame to be rendered. This is only intended to be used internally. /// - void requestFrame(); + Future requestFrame(); /// /// Render a single frame and copy the pixel buffer to [out]. @@ -748,7 +748,7 @@ abstract class ThermionViewer { /// Sets the options for manipulating the camera via the viewport. /// ManipulatorMode.FREE_FLIGHT and ManipulatorMode.MAP are currently unsupported and will throw an exception. /// - @Deprecated("Use ThermionGestureHandler instead") + @Deprecated("Use InputHandler instead") Future setCameraManipulatorOptions( {ManipulatorMode mode = ManipulatorMode.ORBIT, double orbitSpeedX = 0.01, @@ -970,4 +970,14 @@ abstract class ThermionViewer { /// /// Future setActiveCamera(covariant Camera camera); + + /// + /// + /// + Future registerRequestFrameHook(Future Function() hook); + + /// + /// + /// + Future unregisterRequestFrameHook(Future Function() hook); } diff --git a/thermion_dart/lib/thermion_dart/viewer/thermion_viewer_stub.dart b/thermion_dart/lib/thermion_dart/viewer/thermion_viewer_stub.dart index 5f9f2615..adca0182 100644 --- a/thermion_dart/lib/thermion_dart/viewer/thermion_viewer_stub.dart +++ b/thermion_dart/lib/thermion_dart/viewer/thermion_viewer_stub.dart @@ -944,8 +944,8 @@ class ThermionViewerStub extends ThermionViewer { } @override - void requestFrame() { - // TODO: implement requestFrame + Future requestFrame() { + throw UnimplementedError(); } @override @@ -965,6 +965,36 @@ class ThermionViewerStub extends ThermionViewer { // TODO: implement getMainCamera throw UnimplementedError(); } + + @override + Future createCamera() { + // TODO: implement createCamera + throw UnimplementedError(); + } + + @override + Future registerRenderHook(Future Function() hook) { + // TODO: implement registerRenderHook + throw UnimplementedError(); + } + + @override + Future setActiveCamera(covariant Camera camera) { + // TODO: implement setActiveCamera + throw UnimplementedError(); + } + + @override + Future registerRequestFrameHook(Future Function() hook) { + // TODO: implement registerRequestFrameHook + throw UnimplementedError(); + } + + @override + Future unregisterRequestFrameHook(Future Function() hook) { + // TODO: implement unregisterRequestFrameHook + throw UnimplementedError(); + } } diff --git a/thermion_dart/lib/thermion_dart/viewer/web_js/src/thermion_viewer_js.dart b/thermion_dart/lib/thermion_dart/viewer/web_js/src/thermion_viewer_js.dart index 396d8f13..2d2cb478 100644 --- a/thermion_dart/lib/thermion_dart/viewer/web_js/src/thermion_viewer_js.dart +++ b/thermion_dart/lib/thermion_dart/viewer/web_js/src/thermion_viewer_js.dart @@ -1048,10 +1048,6 @@ class ThermionViewerJS implements ThermionViewer { throw UnimplementedError(); } - @override - void requestFrame() { - // TODO: implement requestFrame - } @override // TODO: implement sceneUpdated @@ -1080,4 +1076,34 @@ class ThermionViewerJS implements ThermionViewer { // TODO: implement setCameraLensProjection throw UnimplementedError(); } + + @override + Future createCamera() { + // TODO: implement createCamera + throw UnimplementedError(); + } + + @override + Future registerRequestFrameHook(Future Function() hook) { + // TODO: implement registerRequestFrameHook + throw UnimplementedError(); + } + + @override + Future requestFrame() { + // TODO: implement requestFrame + throw UnimplementedError(); + } + + @override + Future setActiveCamera(covariant Camera camera) { + // TODO: implement setActiveCamera + throw UnimplementedError(); + } + + @override + Future unregisterRequestFrameHook(Future Function() hook) { + // TODO: implement unregisterRequestFrameHook + throw UnimplementedError(); + } } diff --git a/thermion_dart/lib/thermion_dart/viewer/web_wasm/src/camera.dart b/thermion_dart/lib/thermion_dart/viewer/web_wasm/src/camera.dart index 6b04d736..186b53dd 100644 --- a/thermion_dart/lib/thermion_dart/viewer/web_wasm/src/camera.dart +++ b/thermion_dart/lib/thermion_dart/viewer/web_wasm/src/camera.dart @@ -1,6 +1,8 @@ import 'package:thermion_dart/thermion_dart/viewer/shared_types/camera.dart'; import 'package:vector_math/vector_math_64.dart'; +import '../../thermion_viewer_base.dart'; + class ThermionWasmCamera extends Camera { final int pointer; @@ -13,4 +15,22 @@ class ThermionWasmCamera extends Camera { // TODO: implement setProjectionMatrixWithCulling throw UnimplementedError(); } + + @override + Future getModelMatrix() { + // TODO: implement getModelMatrix + throw UnimplementedError(); + } + + @override + Future setLensProjection({double near = kNear, double far = kFar, double aspect = 1.0, double focalLength = kFocalLength}) { + // TODO: implement setLensProjection + throw UnimplementedError(); + } + + @override + Future setTransform(Matrix4 transform) { + // TODO: implement setTransform + throw UnimplementedError(); + } } diff --git a/thermion_dart/lib/thermion_dart/viewer/web_wasm/src/thermion_viewer_wasm.dart b/thermion_dart/lib/thermion_dart/viewer/web_wasm/src/thermion_viewer_wasm.dart index 43a876f6..8aff79ab 100644 --- a/thermion_dart/lib/thermion_dart/viewer/web_wasm/src/thermion_viewer_wasm.dart +++ b/thermion_dart/lib/thermion_dart/viewer/web_wasm/src/thermion_viewer_wasm.dart @@ -2446,7 +2446,7 @@ class ThermionViewerWasm implements ThermionViewer { } @override - void requestFrame() { + Future requestFrame() async { // TODO: implement requestFrame } @@ -2478,4 +2478,32 @@ class ThermionViewerWasm implements ThermionViewer { // TODO: implement setCameraLensProjection throw UnimplementedError(); } + + @override + Future createCamera() { + // TODO: implement createCamera + throw UnimplementedError(); + } + + @override + Future setActiveCamera(covariant Camera camera) { + // TODO: implement setActiveCamera + throw UnimplementedError(); + } + + final _hooks = []; + + @override + Future registerRequestFrameHook(Future Function() hook) async { + if (!_hooks.contains(hook)) { + _hooks.add(hook); + } + } + + @override + Future unregisterRequestFrameHook(Future Function() hook) async { + if (_hooks.contains(hook)) { + _hooks.remove(hook); + } + } } diff --git a/thermion_dart/pubspec.yaml b/thermion_dart/pubspec.yaml index fafad30e..1e08b38a 100644 --- a/thermion_dart/pubspec.yaml +++ b/thermion_dart/pubspec.yaml @@ -22,4 +22,6 @@ dev_dependencies: ffigen: ^12.0.0 test: image: - path: \ No newline at end of file + path: + mockito: ^5.0.0 + build_runner: ^2.0.0 \ No newline at end of file diff --git a/thermion_dart/test/cube.glb b/thermion_dart/test/assets/cube.glb similarity index 100% rename from thermion_dart/test/cube.glb rename to thermion_dart/test/assets/cube.glb diff --git a/thermion_dart/test/assets/cube_texture.svg b/thermion_dart/test/assets/cube_texture.svg new file mode 100644 index 00000000..6d5d700e --- /dev/null +++ b/thermion_dart/test/assets/cube_texture.svg @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + Top + + + Left + + + Front + + + Right + + + Bottom + + + Back + diff --git a/thermion_dart/test/assets/cube_texture_256x256.png b/thermion_dart/test/assets/cube_texture_256x256.png new file mode 100644 index 0000000000000000000000000000000000000000..38c28897fe33ba337f7e719167d7496cd52fa8e8 GIT binary patch literal 4236 zcmd5=_gB-)68}a;r6pKELRV2bNKw=T0!j-~M3f@!N);qP5(vGhpm>9X-ishbMBu_z zLhnVofJzMkq9C165=sIuzVm*3e}L!gXJ^lzGiP^ZXJpMU9iUaW?n)k0Xi8X1W_3!G~GQ&49F5hE2= zYy`E;T+SWDU5I^~S9tEo6`w`>?w-)ab?+fTNr2sbs*VmCt)-);0+zC?W?rb)&?0Ve zfBg70Hd&P;TY)o7@5f0IW2M;7Cbx2qb8rqcuNYw*aSKq6Fip|mt?LgS9QHRoJ!4Ue zQ#ZHSK6e~k7qh)|7*SJW-u0PZLsvH?FAu=Z)y-~I4ZS`KIM|mR`Hk~)>BRs)3@0+5NeuIY_iy~9yelK?HHPqljpaz%5YTH^lv>z8as)Y>TSK$i zA}yWhyJCz)o@n}sVeK-`p_6u9v0#gbttmxAEV;6>wT&VHI{8wN{TQZkeU#Ekk4?4w z8c#t&hULN(ySiMtpcrUbLrqLY1@!&{lCE|;%4Xkp+3yug`S}TimplwxXqBqJo+ZO4u>gD#t+|>5K^s>Ru%hKS51!fUzr*3W{EE}Ko{KVKhE}VY| z;*W_nX?th#Z*3)MRa{y+gci}scQ&JMw0twbO$D8DM9k(mcbdMNTh_3H9^@X7*YhRE z{HEHott)Crc@zyme-3(m#-6cJqZmPj^LmnHj0r@1`qF=6e)8;02i_C?JN$>npDoS3 zfA4olrDSGZ%7NdIepx+|2vgI!Rb>6?wSLC=a1M zLqzjO)SOQM5cXKS1y3aRZ|RCMbvipK=&W?L^tqJ@sZ^zi9CK^U<`l&|-m~7DdG!;t zIcGPVidR)7H$WU|x75DDAVuVhUkcv;u3%->+M9Z@t9zq-Z3jJMdhZuylljGg?uHUZ zF&_1a0_%l4P_ye%#T_;gBP*A_ir7=J_+939<6$>;a($YcL4ARUi%-IiFKz2C-4KHJ z{(Abh7IjcIEMo?gKTa^t3Ox`M4ShU(`GTAr!@Y-3zVR?b-ESK75VyMA!9Kv%heG+? zv^h45w1(#=NS#aYVyv@I`8m+(>hz=z9dKpk0brVCdGl@N*X7bjw6-y89L>pnwESuJ zhzw}`(r$`R^M|#dRby~S%M&Zx{Tv;pQ9wJ#bWht&m_LQs=tSPzZp$_4x2uYYRd(5! z9SAk*>Ccocd-rB*7C%Psht+v)_K6qfqeumf=C!BVJP+ggY);H(w zIcLD$&X<*RBB)lQ>+=W-&97>Kmpxr-%A3##i!T|^yMQ;@XR?m z`{}k0&*c|8=yi{_1W~(Bx$APV>sSkz^!qJ7ej@T6z8MmDHKDj2vA@uCVi(f?+13YmLuR#VvQ?@@X`3I3G zldySPkBoqe>2!DJnFpMiRYGr)PyE%1SC}2>cV1!a0$LLjH#Qau!N%Wef94mQ?p>IE zCZ#Q2P4YfwykS=s)NvKtEx8)sRlBz8LZJmPro>xjz^s1gS(hV(M%na7-a$HJv=srQ zhidH;LfmPZlAk@F2cc)C7^`#TwBy0Xu8Po;zh*7ZRENxNXTu6=O4IK2H-u|ofG`dH z>5t4aJ(uHtAnn`2PJOUw%+;McxE$aE%$D=>5=lBUH|7B^YTPC9!oq*|w?`VMr~M$t zBQWNn6%b}lE_$0U)gqIQo26Dsr6#n0W;A>#Vx*QscfZ>F4 zE_%l;!x1Qy2?ysee@1*?pcVbh>yAh*7<)n1^j(`s8cnio=HG#6@60DnV^GFUXY1If z1Ig{aX4839fr%l;JwEH|u-XHP{KH#vg)7DU*3IK-pWI2rMB)>&^HM{JU0_3$#_?^r z?KTQJSJ5TJBTyT#7J-c2ezD&6(bdMzlZ=JT%UNhly)%I;p)WvQ3utc-XqG{n1z?n*PtT5z=fbINd}gJS9hIoQsJeFwmJ3n_ zS@4+m@1K7Nbyg^EMNdNIv&JhTdSqT;4t79rVnxz)pz!i2dhsPpWaw-lf zC0bAZJOsHm#KRHXC;qCHLc4N3)N6#s)+#?;vsp@^`Vfv@5kLHTbRL~gNxAxnuNYy` zSK492I+y0`8wE^@A`E2|Vm zoY2<)yHG+W$yQG$O!@0<`;tX{P154xb6Gpv)^{e5Q(U;9pMfFFr4h?=`zIqbHF}+) z)6arckp}^aB|!gQ#HpLnQl|O<|Ba3Fi;EBWl?%e6qvL-oM+?gtWLft{L7h~ty(lUz zbvZmROG|4pNW0&=zqTf!Jgj?VwE3Gk#Gx&+;e*XSw5+}o9~UP(H&E8JM!#q7n?OK# z*{xoF?50{!MvS0=Fo6A8)l ziUHp00Cv7~uIZ^+X?))X+GcM0sDG&3M;^N zQ88EXNADtBZ!P;L=J{g~zYfxHc&*nR+nDwX? zBdIVjFM)u9G%PLc_UOiI)5mHjb_PEL>tmjOjUzSjL@<8GfgkQLy@ z=}YoCAAgxw*-4*0*>*2anGEbsonUDC;~`H*|y9ys!19ksVtjuC<6ara#~uz^w&e4<;k&@FF7~V{CJ0kKOVXM zoVk&FXqc@Y5-swY2j6a4KKn38UBQ>E79nLS;?NR)fyJ3r6BCC)RX^gIe|y!s!$vFeV_BJ1O1c|(!wRpv@7^xaroZIyq^7DfLKGy7JBrF6IN=GK z|J`lU9+c0O&Fh7 zC}^L3g+_1DKkeo5`xVr?v<19+S6-gTEeO8(JF&UD`vioCZy|yzOm savePixelBufferToBmp( return data; } +class TestHelper { + late Directory outDir; + + TestHelper(String dir) { + final packageUri = findPackageRoot('thermion_dart'); + testDir = Directory("${packageUri.toFilePath()}/test").path; + + var outDir = Directory("$testDir/output/${dir}"); + // outDir.deleteSync(recursive: true); + outDir.createSync(); + } + + Future capture(ThermionViewer viewer, String outputFilename) async { + await Future.delayed(Duration(milliseconds: 10)); + var outPath = p.join(outDir.path, "$outputFilename.bmp"); + var pixelBuffer = await viewer.capture(); + await savePixelBufferToBmp( + pixelBuffer, + viewer.viewportDimensions.$1.toInt(), + viewer.viewportDimensions.$2.toInt(), + outPath); + return pixelBuffer; + } +} + Future pixelBufferToBmp( Uint8List pixelBuffer, int width, int height) async { final rowSize = (width * 3 + 3) & ~3; diff --git a/thermion_dart/test/input_handlers.dart b/thermion_dart/test/input_handlers.dart new file mode 100644 index 00000000..ac8cdb96 --- /dev/null +++ b/thermion_dart/test/input_handlers.dart @@ -0,0 +1,119 @@ +import 'package:mockito/mockito.dart'; +import 'package:mockito/annotations.dart'; +import 'package:test/test.dart'; +import 'package:thermion_dart/thermion_dart/input/src/input_handler.dart'; +import 'package:thermion_dart/thermion_dart/thermion_viewer.dart'; +import 'package:thermion_dart/thermion_dart/input/src/implementations/fixed_orbit_camera_rotation_delegate.dart'; +import 'package:vector_math/vector_math_64.dart'; + +// Generate mocks +@GenerateMocks([ThermionViewer]) +import 'input_handlers.mocks.dart'; + +void main() { + group('FixedOrbitRotateInputHandlerDelegate Tests', () { + late MockThermionViewer mockViewer; + late FixedOrbitRotateInputHandlerDelegate delegate; + late ThermionEntity mockEntity; + + setUp(() { + mockViewer = MockThermionViewer(); + mockEntity = 0; + + // Setup mock methods + when(mockViewer.getMainCameraEntity()).thenAnswer((_) async => mockEntity); + when(mockViewer.getCameraViewMatrix()).thenAnswer((_) async => Matrix4.identity()); + when(mockViewer.getCameraModelMatrix()).thenAnswer((_) async => Matrix4.translationValues(0, 0, 5)); + when(mockViewer.getCameraProjectionMatrix()).thenAnswer((_) async => Matrix4.identity()); + mockViewer.viewportDimensions = (800, 600); + mockViewer.pixelRatio = 1.0; + + delegate = FixedOrbitRotateInputHandlerDelegate( + mockViewer, + entity: mockEntity, + minimumDistance: 1.0, + ); + }); + + test('queue and execute rotation', () async { + await delegate.queue(InputAction.ROTATE, Vector3(0.1, 0.1, 0)); + await delegate.execute(); + + verify(mockViewer.setTransform(any, captureThat( + predicate((matrix) { + var translation = matrix.getTranslation(); + var rotation = matrix.getRotation(); + + // Check if the camera has rotated + expect(rotation, isNot(equals(Matrix3.identity()))); + + // Check if the distance from origin is maintained + expect(translation.length, closeTo(5.0, 0.01)); + + return true; + }) + ))).called(1); + }); + + test('queue and execute zoom', () async { + await delegate.queue(InputAction.PICK, Vector3(0, 0, 0.1)); + await delegate.execute(); + + verify(mockViewer.setTransform(any, captureThat( + predicate((matrix) { + var translation = matrix.getTranslation(); + + // Check if the camera has moved closer + expect(translation.length, lessThan(5.0)); + + return true; + }) + ))).called(1); + }); + + test('respects minimum distance', () async { + when(mockViewer.getCameraModelMatrix()).thenAnswer((_) async => Matrix4.translationValues(0, 0, 1.5)); + await delegate.queue(InputAction.PICK, Vector3(0, 0, -1)); + await delegate.execute(); + + verify(mockViewer.setTransform(any, captureThat( + predicate((matrix) { + var translation = matrix.getTranslation(); + + // Check if the camera distance is not less than the minimum distance + expect(translation.length, greaterThanOrEqualTo(1.0)); + + return true; + }) + ))).called(1); + }); + + test('ignores translation in fixed orbit mode', () async { + await delegate.queue(InputAction.TRANSLATE, Vector3(1, 1, 1)); + await delegate.execute(); + + verifyNever(mockViewer.setTransform(any, any)); + }); + + test('combined rotation and zoom', () async { + await delegate.queue(InputAction.ROTATE, Vector3(0.1, 0.1, 0)); + await delegate.queue(InputAction.PICK, Vector3(0, 0, 0.1)); + await delegate.execute(); + + verify(mockViewer.setTransform(any, captureThat( + predicate((matrix) { + var translation = matrix.getTranslation(); + var rotation = matrix.getRotation(); + + // Check if the camera has rotated + expect(rotation, isNot(equals(Matrix3.identity()))); + + // Check if the camera has moved closer + expect(translation.length, lessThan(5.0)); + + return true; + }) + ))).called(1); + }); + }); +} \ No newline at end of file diff --git a/thermion_dart/test/input_handlers.mocks.dart b/thermion_dart/test/input_handlers.mocks.dart new file mode 100644 index 00000000..fbdea0fd --- /dev/null +++ b/thermion_dart/test/input_handlers.mocks.dart @@ -0,0 +1,2422 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in thermion_dart/test/input_handlers.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i6; +import 'dart:typed_data' as _i8; + +import 'package:animation_tools_dart/animation_tools_dart.dart' as _i9; +import 'package:mockito/mockito.dart' as _i1; +import 'package:thermion_dart/thermion_dart/viewer/events.dart' as _i7; +import 'package:thermion_dart/thermion_dart/viewer/shared_types/camera.dart' + as _i3; +import 'package:thermion_dart/thermion_dart/viewer/shared_types/shared_types.dart' + as _i4; +import 'package:thermion_dart/thermion_dart/viewer/thermion_viewer_base.dart' + as _i5; +import 'package:vector_math/vector_math_64.dart' as _i2; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeMatrix4_0 extends _i1.SmartFake implements _i2.Matrix4 { + _FakeMatrix4_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeCamera_1 extends _i1.SmartFake implements _i3.Camera { + _FakeCamera_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeVector3_2 extends _i1.SmartFake implements _i2.Vector3 { + _FakeVector3_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeFrustum_3 extends _i1.SmartFake implements _i2.Frustum { + _FakeFrustum_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeMatrix3_4 extends _i1.SmartFake implements _i2.Matrix3 { + _FakeMatrix3_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAabb2_5 extends _i1.SmartFake implements _i2.Aabb2 { + _FakeAabb2_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeThermionTexture_6 extends _i1.SmartFake + implements _i4.ThermionTexture { + _FakeThermionTexture_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeMaterialInstance_7 extends _i1.SmartFake + implements _i4.MaterialInstance { + _FakeMaterialInstance_7( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [ThermionViewer]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockThermionViewer extends _i1.Mock implements _i5.ThermionViewer { + MockThermionViewer() { + _i1.throwOnMissingStub(this); + } + + @override + (double, double) get viewportDimensions => (super.noSuchMethod( + Invocation.getter(#viewportDimensions), + returnValue: (0.0, 0.0), + ) as (double, double)); + + @override + set viewportDimensions((double, double)? _viewportDimensions) => + super.noSuchMethod( + Invocation.setter( + #viewportDimensions, + _viewportDimensions, + ), + returnValueForMissingStub: null, + ); + + @override + double get pixelRatio => (super.noSuchMethod( + Invocation.getter(#pixelRatio), + returnValue: 0.0, + ) as double); + + @override + set pixelRatio(double? _pixelRatio) => super.noSuchMethod( + Invocation.setter( + #pixelRatio, + _pixelRatio, + ), + returnValueForMissingStub: null, + ); + + @override + _i6.Future get initialized => (super.noSuchMethod( + Invocation.getter(#initialized), + returnValue: _i6.Future.value(false), + ) as _i6.Future); + + @override + _i6.Stream<({int entity, double x, double y})> get pickResult => + (super.noSuchMethod( + Invocation.getter(#pickResult), + returnValue: _i6.Stream<({int entity, double x, double y})>.empty(), + ) as _i6.Stream<({int entity, double x, double y})>); + + @override + _i6.Stream<({int entity, double x, double y})> get gizmoPickResult => + (super.noSuchMethod( + Invocation.getter(#gizmoPickResult), + returnValue: _i6.Stream<({int entity, double x, double y})>.empty(), + ) as _i6.Stream<({int entity, double x, double y})>); + + @override + _i6.Stream<_i7.SceneUpdateEvent> get sceneUpdated => (super.noSuchMethod( + Invocation.getter(#sceneUpdated), + returnValue: _i6.Stream<_i7.SceneUpdateEvent>.empty(), + ) as _i6.Stream<_i7.SceneUpdateEvent>); + + @override + bool get rendering => (super.noSuchMethod( + Invocation.getter(#rendering), + returnValue: false, + ) as bool); + + @override + _i6.Future setRendering(bool? render) => (super.noSuchMethod( + Invocation.method( + #setRendering, + [render], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future render() => (super.noSuchMethod( + Invocation.method( + #render, + [], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future requestFrame() => (super.noSuchMethod( + Invocation.method( + #requestFrame, + [], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future<_i8.Uint8List> capture() => (super.noSuchMethod( + Invocation.method( + #capture, + [], + ), + returnValue: _i6.Future<_i8.Uint8List>.value(_i8.Uint8List(0)), + ) as _i6.Future<_i8.Uint8List>); + + @override + _i6.Future setFrameRate(int? framerate) => (super.noSuchMethod( + Invocation.method( + #setFrameRate, + [framerate], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future dispose() => (super.noSuchMethod( + Invocation.method( + #dispose, + [], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setBackgroundImage( + String? path, { + bool? fillHeight = false, + }) => + (super.noSuchMethod( + Invocation.method( + #setBackgroundImage, + [path], + {#fillHeight: fillHeight}, + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setBackgroundImagePosition( + double? x, + double? y, { + bool? clamp = false, + }) => + (super.noSuchMethod( + Invocation.method( + #setBackgroundImagePosition, + [ + x, + y, + ], + {#clamp: clamp}, + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future clearBackgroundImage() => (super.noSuchMethod( + Invocation.method( + #clearBackgroundImage, + [], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setBackgroundColor( + double? r, + double? g, + double? b, + double? alpha, + ) => + (super.noSuchMethod( + Invocation.method( + #setBackgroundColor, + [ + r, + g, + b, + alpha, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future loadSkybox(String? skyboxPath) => (super.noSuchMethod( + Invocation.method( + #loadSkybox, + [skyboxPath], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future removeSkybox() => (super.noSuchMethod( + Invocation.method( + #removeSkybox, + [], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future loadIbl( + String? lightingPath, { + double? intensity = 30000.0, + }) => + (super.noSuchMethod( + Invocation.method( + #loadIbl, + [lightingPath], + {#intensity: intensity}, + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future createIbl( + double? r, + double? g, + double? b, + double? intensity, + ) => + (super.noSuchMethod( + Invocation.method( + #createIbl, + [ + r, + g, + b, + intensity, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future rotateIbl(_i2.Matrix3? rotation) => (super.noSuchMethod( + Invocation.method( + #rotateIbl, + [rotation], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future removeIbl() => (super.noSuchMethod( + Invocation.method( + #removeIbl, + [], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future addLight( + _i4.LightType? type, + double? colour, + double? intensity, + double? posX, + double? posY, + double? posZ, + double? dirX, + double? dirY, + double? dirZ, { + double? falloffRadius = 1.0, + double? spotLightConeInner = 0.39269908169872414, + double? spotLightConeOuter = 0.7853981633974483, + double? sunAngularRadius = 0.545, + double? sunHaloSize = 10.0, + double? sunHaloFallof = 80.0, + bool? castShadows = true, + }) => + (super.noSuchMethod( + Invocation.method( + #addLight, + [ + type, + colour, + intensity, + posX, + posY, + posZ, + dirX, + dirY, + dirZ, + ], + { + #falloffRadius: falloffRadius, + #spotLightConeInner: spotLightConeInner, + #spotLightConeOuter: spotLightConeOuter, + #sunAngularRadius: sunAngularRadius, + #sunHaloSize: sunHaloSize, + #sunHaloFallof: sunHaloFallof, + #castShadows: castShadows, + }, + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + + @override + _i6.Future addDirectLight(_i4.DirectLight? light) => (super.noSuchMethod( + Invocation.method( + #addDirectLight, + [light], + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + + @override + _i6.Future removeLight(int? light) => (super.noSuchMethod( + Invocation.method( + #removeLight, + [light], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future clearLights() => (super.noSuchMethod( + Invocation.method( + #clearLights, + [], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future loadGlb( + String? path, { + int? numInstances = 1, + bool? keepData = false, + }) => + (super.noSuchMethod( + Invocation.method( + #loadGlb, + [path], + { + #numInstances: numInstances, + #keepData: keepData, + }, + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + + @override + _i6.Future loadGlbFromBuffer( + _i8.Uint8List? data, { + int? numInstances = 1, + bool? keepData = false, + int? priority = 4, + int? layer = 0, + }) => + (super.noSuchMethod( + Invocation.method( + #loadGlbFromBuffer, + [data], + { + #numInstances: numInstances, + #keepData: keepData, + #priority: priority, + #layer: layer, + }, + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + + @override + _i6.Future createInstance(int? entity) => (super.noSuchMethod( + Invocation.method( + #createInstance, + [entity], + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + + @override + _i6.Future getInstanceCount(int? entity) => (super.noSuchMethod( + Invocation.method( + #getInstanceCount, + [entity], + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + + @override + _i6.Future> getInstances(int? entity) => (super.noSuchMethod( + Invocation.method( + #getInstances, + [entity], + ), + returnValue: _i6.Future>.value([]), + ) as _i6.Future>); + + @override + _i6.Future loadGltf( + String? path, + String? relativeResourcePath, { + bool? keepData = false, + }) => + (super.noSuchMethod( + Invocation.method( + #loadGltf, + [ + path, + relativeResourcePath, + ], + {#keepData: keepData}, + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + + @override + _i6.Future panStart( + double? x, + double? y, + ) => + (super.noSuchMethod( + Invocation.method( + #panStart, + [ + x, + y, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future panUpdate( + double? x, + double? y, + ) => + (super.noSuchMethod( + Invocation.method( + #panUpdate, + [ + x, + y, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future panEnd() => (super.noSuchMethod( + Invocation.method( + #panEnd, + [], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future rotateStart( + double? x, + double? y, + ) => + (super.noSuchMethod( + Invocation.method( + #rotateStart, + [ + x, + y, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future rotateUpdate( + double? x, + double? y, + ) => + (super.noSuchMethod( + Invocation.method( + #rotateUpdate, + [ + x, + y, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future rotateEnd() => (super.noSuchMethod( + Invocation.method( + #rotateEnd, + [], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setMorphTargetWeights( + int? entity, + List? weights, + ) => + (super.noSuchMethod( + Invocation.method( + #setMorphTargetWeights, + [ + entity, + weights, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future> getMorphTargetNames( + int? entity, + int? childEntity, + ) => + (super.noSuchMethod( + Invocation.method( + #getMorphTargetNames, + [ + entity, + childEntity, + ], + ), + returnValue: _i6.Future>.value([]), + ) as _i6.Future>); + + @override + _i6.Future> getBoneNames( + int? entity, { + int? skinIndex = 0, + }) => + (super.noSuchMethod( + Invocation.method( + #getBoneNames, + [entity], + {#skinIndex: skinIndex}, + ), + returnValue: _i6.Future>.value([]), + ) as _i6.Future>); + + @override + _i6.Future> getAnimationNames(int? entity) => + (super.noSuchMethod( + Invocation.method( + #getAnimationNames, + [entity], + ), + returnValue: _i6.Future>.value([]), + ) as _i6.Future>); + + @override + _i6.Future getAnimationDuration( + int? entity, + int? animationIndex, + ) => + (super.noSuchMethod( + Invocation.method( + #getAnimationDuration, + [ + entity, + animationIndex, + ], + ), + returnValue: _i6.Future.value(0.0), + ) as _i6.Future); + + @override + _i6.Future setMorphAnimationData( + int? entity, + _i9.MorphAnimationData? animation, { + List? targetMeshNames, + }) => + (super.noSuchMethod( + Invocation.method( + #setMorphAnimationData, + [ + entity, + animation, + ], + {#targetMeshNames: targetMeshNames}, + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future clearMorphAnimationData(int? entity) => + (super.noSuchMethod( + Invocation.method( + #clearMorphAnimationData, + [entity], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future resetBones(int? entity) => (super.noSuchMethod( + Invocation.method( + #resetBones, + [entity], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future addBoneAnimation( + int? entity, + _i9.BoneAnimationData? animation, { + int? skinIndex = 0, + double? fadeInInSecs = 0.0, + double? fadeOutInSecs = 0.0, + double? maxDelta = 1.0, + }) => + (super.noSuchMethod( + Invocation.method( + #addBoneAnimation, + [ + entity, + animation, + ], + { + #skinIndex: skinIndex, + #fadeInInSecs: fadeInInSecs, + #fadeOutInSecs: fadeOutInSecs, + #maxDelta: maxDelta, + }, + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future getBone( + int? parent, + int? boneIndex, { + int? skinIndex = 0, + }) => + (super.noSuchMethod( + Invocation.method( + #getBone, + [ + parent, + boneIndex, + ], + {#skinIndex: skinIndex}, + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + + @override + _i6.Future<_i2.Matrix4> getLocalTransform(int? entity) => (super.noSuchMethod( + Invocation.method( + #getLocalTransform, + [entity], + ), + returnValue: _i6.Future<_i2.Matrix4>.value(_FakeMatrix4_0( + this, + Invocation.method( + #getLocalTransform, + [entity], + ), + )), + ) as _i6.Future<_i2.Matrix4>); + + @override + _i6.Future<_i2.Matrix4> getWorldTransform(int? entity) => (super.noSuchMethod( + Invocation.method( + #getWorldTransform, + [entity], + ), + returnValue: _i6.Future<_i2.Matrix4>.value(_FakeMatrix4_0( + this, + Invocation.method( + #getWorldTransform, + [entity], + ), + )), + ) as _i6.Future<_i2.Matrix4>); + + @override + _i6.Future<_i2.Matrix4> getInverseBindMatrix( + int? parent, + int? boneIndex, { + int? skinIndex = 0, + }) => + (super.noSuchMethod( + Invocation.method( + #getInverseBindMatrix, + [ + parent, + boneIndex, + ], + {#skinIndex: skinIndex}, + ), + returnValue: _i6.Future<_i2.Matrix4>.value(_FakeMatrix4_0( + this, + Invocation.method( + #getInverseBindMatrix, + [ + parent, + boneIndex, + ], + {#skinIndex: skinIndex}, + ), + )), + ) as _i6.Future<_i2.Matrix4>); + + @override + _i6.Future setTransform( + int? entity, + _i2.Matrix4? transform, + ) => + (super.noSuchMethod( + Invocation.method( + #setTransform, + [ + entity, + transform, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future updateBoneMatrices(int? entity) => (super.noSuchMethod( + Invocation.method( + #updateBoneMatrices, + [entity], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setBoneTransform( + int? entity, + int? boneIndex, + _i2.Matrix4? transform, { + int? skinIndex = 0, + }) => + (super.noSuchMethod( + Invocation.method( + #setBoneTransform, + [ + entity, + boneIndex, + transform, + ], + {#skinIndex: skinIndex}, + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future removeEntity(int? entity) => (super.noSuchMethod( + Invocation.method( + #removeEntity, + [entity], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future clearEntities() => (super.noSuchMethod( + Invocation.method( + #clearEntities, + [], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future zoomBegin() => (super.noSuchMethod( + Invocation.method( + #zoomBegin, + [], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future zoomUpdate( + double? x, + double? y, + double? z, + ) => + (super.noSuchMethod( + Invocation.method( + #zoomUpdate, + [ + x, + y, + z, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future zoomEnd() => (super.noSuchMethod( + Invocation.method( + #zoomEnd, + [], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future playAnimation( + int? entity, + int? index, { + bool? loop = false, + bool? reverse = false, + bool? replaceActive = true, + double? crossfade = 0.0, + double? startOffset = 0.0, + }) => + (super.noSuchMethod( + Invocation.method( + #playAnimation, + [ + entity, + index, + ], + { + #loop: loop, + #reverse: reverse, + #replaceActive: replaceActive, + #crossfade: crossfade, + #startOffset: startOffset, + }, + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future playAnimationByName( + int? entity, + String? name, { + bool? loop = false, + bool? reverse = false, + bool? replaceActive = true, + double? crossfade = 0.0, + }) => + (super.noSuchMethod( + Invocation.method( + #playAnimationByName, + [ + entity, + name, + ], + { + #loop: loop, + #reverse: reverse, + #replaceActive: replaceActive, + #crossfade: crossfade, + }, + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setAnimationFrame( + int? entity, + int? index, + int? animationFrame, + ) => + (super.noSuchMethod( + Invocation.method( + #setAnimationFrame, + [ + entity, + index, + animationFrame, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future stopAnimation( + int? entity, + int? animationIndex, + ) => + (super.noSuchMethod( + Invocation.method( + #stopAnimation, + [ + entity, + animationIndex, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future stopAnimationByName( + int? entity, + String? name, + ) => + (super.noSuchMethod( + Invocation.method( + #stopAnimationByName, + [ + entity, + name, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setCamera( + int? entity, + String? name, + ) => + (super.noSuchMethod( + Invocation.method( + #setCamera, + [ + entity, + name, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setMainCamera() => (super.noSuchMethod( + Invocation.method( + #setMainCamera, + [], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future getMainCameraEntity() => (super.noSuchMethod( + Invocation.method( + #getMainCameraEntity, + [], + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + + @override + _i6.Future<_i3.Camera> getMainCamera() => (super.noSuchMethod( + Invocation.method( + #getMainCamera, + [], + ), + returnValue: _i6.Future<_i3.Camera>.value(_FakeCamera_1( + this, + Invocation.method( + #getMainCamera, + [], + ), + )), + ) as _i6.Future<_i3.Camera>); + + @override + _i6.Future setCameraFov( + double? degrees, { + bool? horizontal = true, + }) => + (super.noSuchMethod( + Invocation.method( + #setCameraFov, + [degrees], + {#horizontal: horizontal}, + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future getCameraFov(bool? horizontal) => (super.noSuchMethod( + Invocation.method( + #getCameraFov, + [horizontal], + ), + returnValue: _i6.Future.value(0.0), + ) as _i6.Future); + + @override + _i6.Future setToneMapping(_i4.ToneMapper? mapper) => + (super.noSuchMethod( + Invocation.method( + #setToneMapping, + [mapper], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setBloom(double? bloom) => (super.noSuchMethod( + Invocation.method( + #setBloom, + [bloom], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setCameraFocalLength(double? focalLength) => + (super.noSuchMethod( + Invocation.method( + #setCameraFocalLength, + [focalLength], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setCameraCulling( + double? near, + double? far, + ) => + (super.noSuchMethod( + Invocation.method( + #setCameraCulling, + [ + near, + far, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future getCameraCullingNear() => (super.noSuchMethod( + Invocation.method( + #getCameraCullingNear, + [], + ), + returnValue: _i6.Future.value(0.0), + ) as _i6.Future); + + @override + _i6.Future getCameraNear() => (super.noSuchMethod( + Invocation.method( + #getCameraNear, + [], + ), + returnValue: _i6.Future.value(0.0), + ) as _i6.Future); + + @override + _i6.Future getCameraCullingFar() => (super.noSuchMethod( + Invocation.method( + #getCameraCullingFar, + [], + ), + returnValue: _i6.Future.value(0.0), + ) as _i6.Future); + + @override + _i6.Future setCameraLensProjection({ + double? near = 0.05, + double? far = 1000.0, + double? aspect, + double? focalLength = 28.0, + }) => + (super.noSuchMethod( + Invocation.method( + #setCameraLensProjection, + [], + { + #near: near, + #far: far, + #aspect: aspect, + #focalLength: focalLength, + }, + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setCameraFocusDistance(double? focusDistance) => + (super.noSuchMethod( + Invocation.method( + #setCameraFocusDistance, + [focusDistance], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future<_i2.Vector3> getCameraPosition() => (super.noSuchMethod( + Invocation.method( + #getCameraPosition, + [], + ), + returnValue: _i6.Future<_i2.Vector3>.value(_FakeVector3_2( + this, + Invocation.method( + #getCameraPosition, + [], + ), + )), + ) as _i6.Future<_i2.Vector3>); + + @override + _i6.Future<_i2.Matrix4> getCameraModelMatrix() => (super.noSuchMethod( + Invocation.method( + #getCameraModelMatrix, + [], + ), + returnValue: _i6.Future<_i2.Matrix4>.value(_FakeMatrix4_0( + this, + Invocation.method( + #getCameraModelMatrix, + [], + ), + )), + ) as _i6.Future<_i2.Matrix4>); + + @override + _i6.Future<_i2.Matrix4> getCameraViewMatrix() => (super.noSuchMethod( + Invocation.method( + #getCameraViewMatrix, + [], + ), + returnValue: _i6.Future<_i2.Matrix4>.value(_FakeMatrix4_0( + this, + Invocation.method( + #getCameraViewMatrix, + [], + ), + )), + ) as _i6.Future<_i2.Matrix4>); + + @override + _i6.Future<_i2.Matrix4> getCameraProjectionMatrix() => (super.noSuchMethod( + Invocation.method( + #getCameraProjectionMatrix, + [], + ), + returnValue: _i6.Future<_i2.Matrix4>.value(_FakeMatrix4_0( + this, + Invocation.method( + #getCameraProjectionMatrix, + [], + ), + )), + ) as _i6.Future<_i2.Matrix4>); + + @override + _i6.Future<_i2.Matrix4> getCameraCullingProjectionMatrix() => + (super.noSuchMethod( + Invocation.method( + #getCameraCullingProjectionMatrix, + [], + ), + returnValue: _i6.Future<_i2.Matrix4>.value(_FakeMatrix4_0( + this, + Invocation.method( + #getCameraCullingProjectionMatrix, + [], + ), + )), + ) as _i6.Future<_i2.Matrix4>); + + @override + _i6.Future<_i2.Frustum> getCameraFrustum() => (super.noSuchMethod( + Invocation.method( + #getCameraFrustum, + [], + ), + returnValue: _i6.Future<_i2.Frustum>.value(_FakeFrustum_3( + this, + Invocation.method( + #getCameraFrustum, + [], + ), + )), + ) as _i6.Future<_i2.Frustum>); + + @override + _i6.Future setCameraPosition( + double? x, + double? y, + double? z, + ) => + (super.noSuchMethod( + Invocation.method( + #setCameraPosition, + [ + x, + y, + z, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future<_i2.Matrix3> getCameraRotation() => (super.noSuchMethod( + Invocation.method( + #getCameraRotation, + [], + ), + returnValue: _i6.Future<_i2.Matrix3>.value(_FakeMatrix3_4( + this, + Invocation.method( + #getCameraRotation, + [], + ), + )), + ) as _i6.Future<_i2.Matrix3>); + + @override + _i6.Future moveCameraToAsset(int? entity) => (super.noSuchMethod( + Invocation.method( + #moveCameraToAsset, + [entity], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setViewFrustumCulling(bool? enabled) => + (super.noSuchMethod( + Invocation.method( + #setViewFrustumCulling, + [enabled], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setCameraExposure( + double? aperture, + double? shutterSpeed, + double? sensitivity, + ) => + (super.noSuchMethod( + Invocation.method( + #setCameraExposure, + [ + aperture, + shutterSpeed, + sensitivity, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setCameraRotation(_i2.Quaternion? quaternion) => + (super.noSuchMethod( + Invocation.method( + #setCameraRotation, + [quaternion], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setCameraModelMatrix(List? matrix) => + (super.noSuchMethod( + Invocation.method( + #setCameraModelMatrix, + [matrix], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setCameraModelMatrix4(_i2.Matrix4? matrix) => + (super.noSuchMethod( + Invocation.method( + #setCameraModelMatrix4, + [matrix], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setMaterialColor( + int? entity, + String? meshName, + int? materialIndex, + double? r, + double? g, + double? b, + double? a, + ) => + (super.noSuchMethod( + Invocation.method( + #setMaterialColor, + [ + entity, + meshName, + materialIndex, + r, + g, + b, + a, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setMaterialPropertyFloat4( + int? entity, + String? propertyName, + int? materialIndex, + double? f1, + double? f2, + double? f3, + double? f4, + ) => + (super.noSuchMethod( + Invocation.method( + #setMaterialPropertyFloat4, + [ + entity, + propertyName, + materialIndex, + f1, + f2, + f3, + f4, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setMaterialPropertyFloat( + int? entity, + String? propertyName, + int? materialIndex, + double? value, + ) => + (super.noSuchMethod( + Invocation.method( + #setMaterialPropertyFloat, + [ + entity, + propertyName, + materialIndex, + value, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setMaterialPropertyInt( + int? entity, + String? propertyName, + int? materialIndex, + int? value, + ) => + (super.noSuchMethod( + Invocation.method( + #setMaterialPropertyInt, + [ + entity, + propertyName, + materialIndex, + value, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future transformToUnitCube(int? entity) => (super.noSuchMethod( + Invocation.method( + #transformToUnitCube, + [entity], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setPosition( + int? entity, + double? x, + double? y, + double? z, + ) => + (super.noSuchMethod( + Invocation.method( + #setPosition, + [ + entity, + x, + y, + z, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setLightPosition( + int? lightEntity, + double? x, + double? y, + double? z, + ) => + (super.noSuchMethod( + Invocation.method( + #setLightPosition, + [ + lightEntity, + x, + y, + z, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setLightDirection( + int? lightEntity, + _i2.Vector3? direction, + ) => + (super.noSuchMethod( + Invocation.method( + #setLightDirection, + [ + lightEntity, + direction, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setScale( + int? entity, + double? scale, + ) => + (super.noSuchMethod( + Invocation.method( + #setScale, + [ + entity, + scale, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setRotation( + int? entity, + double? rads, + double? x, + double? y, + double? z, + ) => + (super.noSuchMethod( + Invocation.method( + #setRotation, + [ + entity, + rads, + x, + y, + z, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future queuePositionUpdate( + int? entity, + double? x, + double? y, + double? z, { + bool? relative = false, + }) => + (super.noSuchMethod( + Invocation.method( + #queuePositionUpdate, + [ + entity, + x, + y, + z, + ], + {#relative: relative}, + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future queuePositionUpdateFromViewportCoords( + int? entity, + double? x, + double? y, + ) => + (super.noSuchMethod( + Invocation.method( + #queuePositionUpdateFromViewportCoords, + [ + entity, + x, + y, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future queueRelativePositionUpdateWorldAxis( + int? entity, + double? viewportX, + double? viewportY, + double? x, + double? y, + double? z, + ) => + (super.noSuchMethod( + Invocation.method( + #queueRelativePositionUpdateWorldAxis, + [ + entity, + viewportX, + viewportY, + x, + y, + z, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future queueRotationUpdate( + int? entity, + double? rads, + double? x, + double? y, + double? z, { + bool? relative = false, + }) => + (super.noSuchMethod( + Invocation.method( + #queueRotationUpdate, + [ + entity, + rads, + x, + y, + z, + ], + {#relative: relative}, + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future queueRotationUpdateQuat( + int? entity, + _i2.Quaternion? quat, { + bool? relative = false, + }) => + (super.noSuchMethod( + Invocation.method( + #queueRotationUpdateQuat, + [ + entity, + quat, + ], + {#relative: relative}, + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setPostProcessing(bool? enabled) => (super.noSuchMethod( + Invocation.method( + #setPostProcessing, + [enabled], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setShadowsEnabled(bool? enabled) => (super.noSuchMethod( + Invocation.method( + #setShadowsEnabled, + [enabled], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setShadowType(_i4.ShadowType? shadowType) => + (super.noSuchMethod( + Invocation.method( + #setShadowType, + [shadowType], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setSoftShadowOptions( + double? penumbraScale, + double? penumbraRatioScale, + ) => + (super.noSuchMethod( + Invocation.method( + #setSoftShadowOptions, + [ + penumbraScale, + penumbraRatioScale, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setAntiAliasing( + bool? msaa, + bool? fxaa, + bool? taa, + ) => + (super.noSuchMethod( + Invocation.method( + #setAntiAliasing, + [ + msaa, + fxaa, + taa, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setRotationQuat( + int? entity, + _i2.Quaternion? rotation, + ) => + (super.noSuchMethod( + Invocation.method( + #setRotationQuat, + [ + entity, + rotation, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future reveal( + int? entity, + String? meshName, + ) => + (super.noSuchMethod( + Invocation.method( + #reveal, + [ + entity, + meshName, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future hide( + int? entity, + String? meshName, + ) => + (super.noSuchMethod( + Invocation.method( + #hide, + [ + entity, + meshName, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + void pick( + int? x, + int? y, + ) => + super.noSuchMethod( + Invocation.method( + #pick, + [ + x, + y, + ], + ), + returnValueForMissingStub: null, + ); + + @override + void pickGizmo( + int? x, + int? y, + ) => + super.noSuchMethod( + Invocation.method( + #pickGizmo, + [ + x, + y, + ], + ), + returnValueForMissingStub: null, + ); + + @override + String? getNameForEntity(int? entity) => + (super.noSuchMethod(Invocation.method( + #getNameForEntity, + [entity], + )) as String?); + + @override + _i6.Future setCameraManipulatorOptions({ + _i4.ManipulatorMode? mode = _i4.ManipulatorMode.ORBIT, + double? orbitSpeedX = 0.01, + double? orbitSpeedY = 0.01, + double? zoomSpeed = 0.01, + }) => + (super.noSuchMethod( + Invocation.method( + #setCameraManipulatorOptions, + [], + { + #mode: mode, + #orbitSpeedX: orbitSpeedX, + #orbitSpeedY: orbitSpeedY, + #zoomSpeed: zoomSpeed, + }, + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future> getChildEntities( + int? parent, + bool? renderableOnly, + ) => + (super.noSuchMethod( + Invocation.method( + #getChildEntities, + [ + parent, + renderableOnly, + ], + ), + returnValue: _i6.Future>.value([]), + ) as _i6.Future>); + + @override + _i6.Future getChildEntity( + int? parent, + String? childName, + ) => + (super.noSuchMethod( + Invocation.method( + #getChildEntity, + [ + parent, + childName, + ], + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + + @override + _i6.Future> getChildEntityNames( + int? entity, { + bool? renderableOnly = true, + }) => + (super.noSuchMethod( + Invocation.method( + #getChildEntityNames, + [entity], + {#renderableOnly: renderableOnly}, + ), + returnValue: _i6.Future>.value([]), + ) as _i6.Future>); + + @override + _i6.Future setRecording(bool? recording) => (super.noSuchMethod( + Invocation.method( + #setRecording, + [recording], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setRecordingOutputDirectory(String? outputDirectory) => + (super.noSuchMethod( + Invocation.method( + #setRecordingOutputDirectory, + [outputDirectory], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future addAnimationComponent(int? entity) => (super.noSuchMethod( + Invocation.method( + #addAnimationComponent, + [entity], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future removeAnimationComponent(int? entity) => + (super.noSuchMethod( + Invocation.method( + #removeAnimationComponent, + [entity], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future addCollisionComponent( + int? entity, { + void Function( + int, + int, + )? callback, + bool? affectsTransform = false, + }) => + (super.noSuchMethod( + Invocation.method( + #addCollisionComponent, + [entity], + { + #callback: callback, + #affectsTransform: affectsTransform, + }, + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future removeCollisionComponent(int? entity) => + (super.noSuchMethod( + Invocation.method( + #removeCollisionComponent, + [entity], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future createGeometry( + _i4.Geometry? geometry, { + _i4.MaterialInstance? materialInstance, + bool? keepData = false, + }) => + (super.noSuchMethod( + Invocation.method( + #createGeometry, + [geometry], + { + #materialInstance: materialInstance, + #keepData: keepData, + }, + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future getParent(int? entity) => (super.noSuchMethod( + Invocation.method( + #getParent, + [entity], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future getAncestor(int? entity) => (super.noSuchMethod( + Invocation.method( + #getAncestor, + [entity], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setParent( + int? child, + int? parent, { + bool? preserveScaling, + }) => + (super.noSuchMethod( + Invocation.method( + #setParent, + [ + child, + parent, + ], + {#preserveScaling: preserveScaling}, + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future testCollisions(int? entity) => (super.noSuchMethod( + Invocation.method( + #testCollisions, + [entity], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setPriority( + int? entityId, + int? priority, + ) => + (super.noSuchMethod( + Invocation.method( + #setPriority, + [ + entityId, + priority, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + void onDispose(_i6.Future Function()? callback) => + super.noSuchMethod( + Invocation.method( + #onDispose, + [callback], + ), + returnValueForMissingStub: null, + ); + + @override + _i6.Future<_i2.Aabb2> getViewportBoundingBox(int? entity) => + (super.noSuchMethod( + Invocation.method( + #getViewportBoundingBox, + [entity], + ), + returnValue: _i6.Future<_i2.Aabb2>.value(_FakeAabb2_5( + this, + Invocation.method( + #getViewportBoundingBox, + [entity], + ), + )), + ) as _i6.Future<_i2.Aabb2>); + + @override + _i6.Future setLayerVisibility( + int? layer, + bool? visible, + ) => + (super.noSuchMethod( + Invocation.method( + #setLayerVisibility, + [ + layer, + visible, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setVisibilityLayer( + int? entity, + int? layer, + ) => + (super.noSuchMethod( + Invocation.method( + #setVisibilityLayer, + [ + entity, + layer, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setGizmoVisibility(bool? visible) => (super.noSuchMethod( + Invocation.method( + #setGizmoVisibility, + [visible], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setStencilHighlight( + int? entity, { + double? r = 1.0, + double? g = 0.0, + double? b = 0.0, + }) => + (super.noSuchMethod( + Invocation.method( + #setStencilHighlight, + [entity], + { + #r: r, + #g: g, + #b: b, + }, + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future removeStencilHighlight(int? entity) => + (super.noSuchMethod( + Invocation.method( + #removeStencilHighlight, + [entity], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future<_i4.ThermionTexture> createTexture(_i8.Uint8List? data) => + (super.noSuchMethod( + Invocation.method( + #createTexture, + [data], + ), + returnValue: + _i6.Future<_i4.ThermionTexture>.value(_FakeThermionTexture_6( + this, + Invocation.method( + #createTexture, + [data], + ), + )), + ) as _i6.Future<_i4.ThermionTexture>); + + @override + _i6.Future applyTexture( + _i4.ThermionTexture? texture, + int? entity, { + int? materialIndex = 0, + String? parameterName = r'baseColorMap', + }) => + (super.noSuchMethod( + Invocation.method( + #applyTexture, + [ + texture, + entity, + ], + { + #materialIndex: materialIndex, + #parameterName: parameterName, + }, + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future destroyTexture(_i4.ThermionTexture? texture) => + (super.noSuchMethod( + Invocation.method( + #destroyTexture, + [texture], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future<_i4.MaterialInstance> createUbershaderMaterialInstance({ + bool? doubleSided = false, + bool? unlit = false, + bool? hasVertexColors = false, + bool? hasBaseColorTexture = false, + bool? hasNormalTexture = false, + bool? hasOcclusionTexture = false, + bool? hasEmissiveTexture = false, + bool? useSpecularGlossiness = false, + _i4.AlphaMode? alphaMode = _i4.AlphaMode.OPAQUE, + bool? enableDiagnostics = false, + bool? hasMetallicRoughnessTexture = false, + int? metallicRoughnessUV = 0, + int? baseColorUV = 0, + bool? hasClearCoatTexture = false, + int? clearCoatUV = 0, + bool? hasClearCoatRoughnessTexture = false, + int? clearCoatRoughnessUV = 0, + bool? hasClearCoatNormalTexture = false, + int? clearCoatNormalUV = 0, + bool? hasClearCoat = false, + bool? hasTransmission = false, + bool? hasTextureTransforms = false, + int? emissiveUV = 0, + int? aoUV = 0, + int? normalUV = 0, + bool? hasTransmissionTexture = false, + int? transmissionUV = 0, + bool? hasSheenColorTexture = false, + int? sheenColorUV = 0, + bool? hasSheenRoughnessTexture = false, + int? sheenRoughnessUV = 0, + bool? hasVolumeThicknessTexture = false, + int? volumeThicknessUV = 0, + bool? hasSheen = false, + bool? hasIOR = false, + bool? hasVolume = false, + }) => + (super.noSuchMethod( + Invocation.method( + #createUbershaderMaterialInstance, + [], + { + #doubleSided: doubleSided, + #unlit: unlit, + #hasVertexColors: hasVertexColors, + #hasBaseColorTexture: hasBaseColorTexture, + #hasNormalTexture: hasNormalTexture, + #hasOcclusionTexture: hasOcclusionTexture, + #hasEmissiveTexture: hasEmissiveTexture, + #useSpecularGlossiness: useSpecularGlossiness, + #alphaMode: alphaMode, + #enableDiagnostics: enableDiagnostics, + #hasMetallicRoughnessTexture: hasMetallicRoughnessTexture, + #metallicRoughnessUV: metallicRoughnessUV, + #baseColorUV: baseColorUV, + #hasClearCoatTexture: hasClearCoatTexture, + #clearCoatUV: clearCoatUV, + #hasClearCoatRoughnessTexture: hasClearCoatRoughnessTexture, + #clearCoatRoughnessUV: clearCoatRoughnessUV, + #hasClearCoatNormalTexture: hasClearCoatNormalTexture, + #clearCoatNormalUV: clearCoatNormalUV, + #hasClearCoat: hasClearCoat, + #hasTransmission: hasTransmission, + #hasTextureTransforms: hasTextureTransforms, + #emissiveUV: emissiveUV, + #aoUV: aoUV, + #normalUV: normalUV, + #hasTransmissionTexture: hasTransmissionTexture, + #transmissionUV: transmissionUV, + #hasSheenColorTexture: hasSheenColorTexture, + #sheenColorUV: sheenColorUV, + #hasSheenRoughnessTexture: hasSheenRoughnessTexture, + #sheenRoughnessUV: sheenRoughnessUV, + #hasVolumeThicknessTexture: hasVolumeThicknessTexture, + #volumeThicknessUV: volumeThicknessUV, + #hasSheen: hasSheen, + #hasIOR: hasIOR, + #hasVolume: hasVolume, + }, + ), + returnValue: + _i6.Future<_i4.MaterialInstance>.value(_FakeMaterialInstance_7( + this, + Invocation.method( + #createUbershaderMaterialInstance, + [], + { + #doubleSided: doubleSided, + #unlit: unlit, + #hasVertexColors: hasVertexColors, + #hasBaseColorTexture: hasBaseColorTexture, + #hasNormalTexture: hasNormalTexture, + #hasOcclusionTexture: hasOcclusionTexture, + #hasEmissiveTexture: hasEmissiveTexture, + #useSpecularGlossiness: useSpecularGlossiness, + #alphaMode: alphaMode, + #enableDiagnostics: enableDiagnostics, + #hasMetallicRoughnessTexture: hasMetallicRoughnessTexture, + #metallicRoughnessUV: metallicRoughnessUV, + #baseColorUV: baseColorUV, + #hasClearCoatTexture: hasClearCoatTexture, + #clearCoatUV: clearCoatUV, + #hasClearCoatRoughnessTexture: hasClearCoatRoughnessTexture, + #clearCoatRoughnessUV: clearCoatRoughnessUV, + #hasClearCoatNormalTexture: hasClearCoatNormalTexture, + #clearCoatNormalUV: clearCoatNormalUV, + #hasClearCoat: hasClearCoat, + #hasTransmission: hasTransmission, + #hasTextureTransforms: hasTextureTransforms, + #emissiveUV: emissiveUV, + #aoUV: aoUV, + #normalUV: normalUV, + #hasTransmissionTexture: hasTransmissionTexture, + #transmissionUV: transmissionUV, + #hasSheenColorTexture: hasSheenColorTexture, + #sheenColorUV: sheenColorUV, + #hasSheenRoughnessTexture: hasSheenRoughnessTexture, + #sheenRoughnessUV: sheenRoughnessUV, + #hasVolumeThicknessTexture: hasVolumeThicknessTexture, + #volumeThicknessUV: volumeThicknessUV, + #hasSheen: hasSheen, + #hasIOR: hasIOR, + #hasVolume: hasVolume, + }, + ), + )), + ) as _i6.Future<_i4.MaterialInstance>); + + @override + _i6.Future destroyMaterialInstance( + _i4.MaterialInstance? materialInstance) => + (super.noSuchMethod( + Invocation.method( + #destroyMaterialInstance, + [materialInstance], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future<_i4.MaterialInstance> createUnlitMaterialInstance() => + (super.noSuchMethod( + Invocation.method( + #createUnlitMaterialInstance, + [], + ), + returnValue: + _i6.Future<_i4.MaterialInstance>.value(_FakeMaterialInstance_7( + this, + Invocation.method( + #createUnlitMaterialInstance, + [], + ), + )), + ) as _i6.Future<_i4.MaterialInstance>); + + @override + _i6.Future<_i4.MaterialInstance?> getMaterialInstanceAt( + int? entity, + int? index, + ) => + (super.noSuchMethod( + Invocation.method( + #getMaterialInstanceAt, + [ + entity, + index, + ], + ), + returnValue: _i6.Future<_i4.MaterialInstance?>.value(), + ) as _i6.Future<_i4.MaterialInstance?>); + + @override + _i6.Future<_i3.Camera> createCamera() => (super.noSuchMethod( + Invocation.method( + #createCamera, + [], + ), + returnValue: _i6.Future<_i3.Camera>.value(_FakeCamera_1( + this, + Invocation.method( + #createCamera, + [], + ), + )), + ) as _i6.Future<_i3.Camera>); + + @override + _i6.Future setActiveCamera(_i3.Camera? camera) => + (super.noSuchMethod( + Invocation.method( + #setActiveCamera, + [camera], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future registerRequestFrameHook( + _i6.Future Function()? hook) => + (super.noSuchMethod( + Invocation.method( + #registerRequestFrameHook, + [hook], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future unregisterRequestFrameHook( + _i6.Future Function()? hook) => + (super.noSuchMethod( + Invocation.method( + #unregisterRequestFrameHook, + [hook], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); +} diff --git a/thermion_dart/test/integration_test.dart b/thermion_dart/test/integration_test.dart index d1e6c794..292a8f97 100644 --- a/thermion_dart/test/integration_test.dart +++ b/thermion_dart/test/integration_test.dart @@ -7,169 +7,27 @@ import 'package:image/image.dart'; import 'package:thermion_dart/thermion_dart.dart'; import 'package:test/test.dart'; import 'package:animation_tools_dart/animation_tools_dart.dart'; -import 'package:path/path.dart' as p; -import 'package:thermion_dart/thermion_dart/utils/geometry.dart'; import 'package:thermion_dart/thermion_dart/viewer/events.dart'; import 'package:thermion_dart/thermion_dart/viewer/ffi/src/thermion_viewer_ffi.dart'; import 'package:vector_math/vector_math_64.dart'; import 'helpers.dart'; -Color kWhite = ColorFloat32(4)..setRgba(1.0, 1.0, 1.0, 1.0); -Color kRed = ColorFloat32(4)..setRgba(1.0, 0.0, 0.0, 1.0); - void main() async { - final packageUri = findPackageRoot('thermion_dart'); - testDir = Directory("${packageUri.toFilePath()}/test").path; - - var outDir = Directory("$testDir/output"); - - // outDir.deleteSync(recursive: true); - outDir.createSync(); - - Future _capture(ThermionViewer viewer, String outputFilename) async { - await Future.delayed(Duration(milliseconds: 10)); - var outPath = p.join(outDir.path, "$outputFilename.bmp"); - var pixelBuffer = await viewer.capture(); - await savePixelBufferToBmp( - pixelBuffer, - viewer.viewportDimensions.$1.toInt(), - viewer.viewportDimensions.$2.toInt(), - outPath); - return pixelBuffer; - } - - group('camera', () { - test('getCameraModelMatrix, getCameraPosition, rotation', () async { - var viewer = await createViewer(); - var matrix = await viewer.getCameraModelMatrix(); - expect(matrix.trace(), 4); - await viewer.setCameraPosition(2.0, 2.0, 2.0); - matrix = await viewer.getCameraModelMatrix(); - var position = matrix.getColumn(3).xyz; - expect(position.x, 2.0); - expect(position.y, 2.0); - expect(position.z, 2.0); - - position = await viewer.getCameraPosition(); - expect(position.x, 2.0); - expect(position.y, 2.0); - expect(position.z, 2.0); - }); - - test('getCameraViewMatrix', () async { - var viewer = await createViewer(); - - var modelMatrix = await viewer.getCameraModelMatrix(); - var viewMatrix = await viewer.getCameraViewMatrix(); - - // The view matrix should be the inverse of the model matrix - var identity = modelMatrix * viewMatrix; - expect(identity.isIdentity(), isTrue); - - // Check that moving the camera affects the view matrix - await viewer.setCameraPosition(3.0, 4.0, 5.0); - viewMatrix = await viewer.getCameraViewMatrix(); - var invertedView = viewMatrix.clone()..invert(); - var position = invertedView.getColumn(3).xyz; - expect(position.x, closeTo(3.0, 1e-6)); - expect(position.y, closeTo(4.0, 1e-6)); - expect(position.z, closeTo(5.0, 1e-6)); - }); - - test('getCameraProjectionMatrix', () async { - var viewer = await createViewer(); - var projectionMatrix = await viewer.getCameraProjectionMatrix(); - print(projectionMatrix); - }); - - test('getCameraCullingProjectionMatrix', () async { - var viewer = await createViewer(); - var matrix = await viewer.getCameraCullingProjectionMatrix(); - print(matrix); - throw Exception("TODO"); - }); - - test('getCameraFrustum', () async { - var viewer = await createViewer(); - var frustum = await viewer.getCameraFrustum(); - print(frustum.plane5.normal); - print(frustum.plane5.constant); - - await viewer.setCameraLensProjection( - near: 10.0, far: 1000.0, aspect: 1.0, focalLength: 28.0); - frustum = await viewer.getCameraFrustum(); - print(frustum.plane5.normal); - print(frustum.plane5.constant); - }); - - test('set custom projection/culling matrix', () async { - var viewer = - await createViewer(bg: kRed, cameraPosition: Vector3(0, 0, 4)); - var camera = await viewer.getMainCamera(); - final cube = await viewer.createGeometry(GeometryHelper.cube()); - - // cube is visible when inside the frustum, cube is visible - var projectionMatrix = - makeOrthographicMatrix(-10.0, 10.0, -10.0, 10.0, 0.05, 10000); - await camera.setProjectionMatrixWithCulling( - projectionMatrix, 0.05, 10000); - await _capture( - viewer, "camera_projection_culling_matrix_object_in_frustum"); - - // cube no longer visible when the far plane is moved closer to camera so cube is outside - projectionMatrix = - makeOrthographicMatrix(-10.0, 10.0, -10.0, 10.0, 0.05, 1); - await camera.setProjectionMatrixWithCulling(projectionMatrix, 0.05, 1); - await _capture( - viewer, "camera_projection_culling_matrix_object_outside_frustum"); - }); - - test('setting camera transform updates model matrix', () async { - var viewer = await createViewer(); - - var cameraEntity = await viewer.getMainCameraEntity(); - var camera = await viewer.getMainCamera(); - - await viewer.setPosition(cameraEntity, 1, 0, 0); - - var modelMatrix = await viewer.getCameraModelMatrix(); - expect(modelMatrix.getColumn(3).x, 1.0); - expect(modelMatrix.getColumn(3).y, 0.0); - expect(modelMatrix.getColumn(3).z, 0.0); - expect(modelMatrix.getColumn(3).w, 1.0); - }); - - test('create camera', () async { - var viewer = await createViewer(); - - await viewer.setCameraPosition(0, 0, 5); - await viewer.setBackgroundColor(1.0, 0.0, 1.0, 1.0); - await viewer.createGeometry(GeometryHelper.cube()); - await _capture(viewer, "create_camera_main_camera"); - var newCamera = await viewer.createCamera(); - await newCamera.setTransform(Matrix4.translation(Vector3(0, 0, 4))); - newCamera.setLensProjection(); - await viewer.setActiveCamera(newCamera); - await _capture(viewer, "create_camera_new_camera"); - final mainCamera = await viewer.getMainCamera(); - await viewer.setActiveCamera(mainCamera); - await _capture(viewer, "create_camera_back_to_main"); - }); - }); + final testHelper = TestHelper("integration"); group('background', () { test('set background color to solid green', () async { var viewer = await createViewer(); await viewer.setBackgroundColor(0.0, 1.0, 0.0, 1.0); - await _capture(viewer, "set_background_color_to_solid_green"); + await testHelper.capture(viewer, "set_background_color_to_solid_green"); await viewer.dispose(); }); test('set background color to full transparency', () async { var viewer = await createViewer(); await viewer.setBackgroundColor(0.0, 1.0, 0.0, 0.0); - await _capture(viewer, "set_background_color_to_transparent_green"); + await testHelper.capture(viewer, "set_background_color_to_transparent_green"); await viewer.dispose(); }); @@ -178,7 +36,7 @@ void main() async { await viewer.loadSkybox( "file:///$testDir/../../examples/assets/default_env/default_env_skybox.ktx"); await Future.delayed(Duration(seconds: 1)); - await _capture(viewer, "skybox"); + await testHelper.capture(viewer, "skybox"); }); test('set background image', () async { @@ -187,7 +45,7 @@ void main() async { .setBackgroundImage("file:///$testDir/cube_texture_512x512.png"); await viewer.setPostProcessing(true); await viewer.setToneMapping(ToneMapper.LINEAR); - await _capture(viewer, "set_background_image"); + await testHelper.capture(viewer, "set_background_image"); await viewer.dispose(); }); }); @@ -201,7 +59,7 @@ void main() async { await viewer.setCameraPosition(0, 1, 5); await viewer .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5)); - await _capture(viewer, "load_glb_from_file"); + await testHelper.capture(viewer, "load_glb_from_file"); }); test('load glb from buffer', () async { @@ -213,7 +71,7 @@ void main() async { await viewer.setCameraPosition(0, 1, 5); await viewer .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5)); - await _capture(viewer, "load_glb_from_buffer"); + await testHelper.capture(viewer, "load_glb_from_buffer"); }); test('load glb from buffer with priority', () async { @@ -236,7 +94,7 @@ void main() async { await viewer.setMaterialPropertyFloat4( entity, "baseColorFactor", 0, 0, 1.0, 0.0, 1.0); } - await _capture(viewer, "load_glb_from_buffer_with_priority"); + await testHelper.capture(viewer, "load_glb_from_buffer_with_priority"); }); }); @@ -370,7 +228,7 @@ void main() async { await viewer .createGeometry(GeometryHelper.cube(normals: false, uvs: false)); - await _capture(viewer, "geometry_cube_no_uv_no_normal"); + await testHelper.capture(viewer, "geometry_cube_no_uv_no_normal"); }); test('create cube (no normals)', () async { @@ -384,7 +242,7 @@ void main() async { await viewer.setBackgroundColor(1.0, 0.0, 1.0, 1.0); await viewer .createGeometry(GeometryHelper.cube(normals: false, uvs: false)); - await _capture(viewer, "geometry_cube_no_normals"); + await testHelper.capture(viewer, "geometry_cube_no_normals"); }); test('create cube (with normals)', () async { @@ -399,7 +257,7 @@ void main() async { await viewer.setBackgroundColor(1.0, 1.0, 1.0, 1.0); await viewer .createGeometry(GeometryHelper.cube(normals: true, uvs: false)); - await _capture(viewer, "geometry_cube_with_normals"); + await testHelper.capture(viewer, "geometry_cube_with_normals"); }); test('create cube with custom ubershader material instance (color)', @@ -418,7 +276,7 @@ void main() async { materialInstance: materialInstance); await viewer.setMaterialPropertyFloat4( cube, "baseColorFactor", 0, 0.0, 1.0, 0.0, 0.0); - await _capture(viewer, "geometry_cube_with_custom_material_ubershader"); + await testHelper.capture(viewer, "geometry_cube_with_custom_material_ubershader"); await viewer.removeEntity(cube); await viewer.destroyMaterialInstance(materialInstance); }); @@ -440,7 +298,7 @@ void main() async { File("$testDir/cube_texture_512x512.png").readAsBytesSync(); var texture = await viewer.createTexture(textureData); await viewer.applyTexture(texture as ThermionFFITexture, cube); - await _capture( + await testHelper.capture( viewer, "geometry_cube_with_custom_material_ubershader_texture"); await viewer.removeEntity(cube); await viewer.destroyMaterialInstance(materialInstance); @@ -464,7 +322,7 @@ void main() async { File("$testDir/cube_texture_512x512.png").readAsBytesSync(); var texture = await viewer.createTexture(textureData); await viewer.applyTexture(texture, cube); - await _capture( + await testHelper.capture( viewer, "geometry_cube_with_custom_material_unlit_texture_only"); await viewer.removeEntity(cube); @@ -474,7 +332,7 @@ void main() async { await viewer.setMaterialPropertyInt(cube, "baseColorIndex", 0, -1); await viewer.setMaterialPropertyFloat4( cube, "baseColorFactor", 0, 0.0, 1.0, 0.0, 1.0); - await _capture( + await testHelper.capture( viewer, "geometry_cube_with_custom_material_unlit_color_only"); await viewer.removeEntity(cube); @@ -486,7 +344,7 @@ void main() async { cube, "baseColorFactor", 0, 0.0, 1.0, 0.0, 0.5); await viewer.applyTexture(texture, cube); - await _capture( + await testHelper.capture( viewer, "geometry_cube_with_custom_material_unlit_color_and_texture"); await viewer.removeEntity(cube); @@ -502,7 +360,7 @@ void main() async { await viewer.setCameraPosition(0, 0, 6); await viewer .createGeometry(GeometryHelper.sphere(normals: false, uvs: false)); - await _capture(viewer, "geometry_sphere_no_normals"); + await testHelper.capture(viewer, "geometry_sphere_no_normals"); }); }); @@ -525,16 +383,16 @@ void main() async { expect(materialInstance, isNotNull); // with depth write enabled on both materials, cube2 renders behind the white cube - await _capture(viewer, "material_instance_depth_write_enabled"); + await testHelper.capture(viewer, "material_instance_depth_write_enabled"); // if we disable depth write on cube1, then cube2 will always appear in front // (relying on insertion order) materialInstance!.setDepthWriteEnabled(false); - await _capture(viewer, "material_instance_depth_write_disabled"); + await testHelper.capture(viewer, "material_instance_depth_write_disabled"); // set priority for the cube1 cube to 7 (render) last, cube1 renders in front await viewer.setPriority(cube1, 7); - await _capture( + await testHelper.capture( viewer, "material_instance_depth_write_disabled_with_priority"); }); }); @@ -549,7 +407,7 @@ void main() async { // await viewer // .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5)); // await viewer.setRendering(true); - // await _capture(viewer, "glb_create_instance"); + // await testHelper.capture(viewer, "glb_create_instance"); // await viewer.setRendering(false); // }); @@ -605,10 +463,10 @@ void main() async { final cube = await viewer.createGeometry(GeometryHelper.cube()); - await _capture(viewer, "set_material_float4_pre"); + await testHelper.capture(viewer, "set_material_float4_pre"); await viewer.setMaterialPropertyFloat4( cube, "baseColorFactor", 0, 0.0, 1.0, 0.0, 1.0); - await _capture(viewer, "set_material_float4_post"); + await testHelper.capture(viewer, "set_material_float4_post"); }); test('set float material property for custom geometry', () async { var viewer = await createViewer(); @@ -622,9 +480,9 @@ void main() async { // this won't actually do anything because the default ubershader doesn't use specular/glossiness // but we can at least check that the call succeeds - await _capture(viewer, "set_material_specular_pre"); + await testHelper.capture(viewer, "set_material_specular_pre"); await viewer.setMaterialPropertyFloat(cube, "specularFactor", 0, 0.0); - await _capture(viewer, "set_material_specular_post"); + await testHelper.capture(viewer, "set_material_specular_post"); }); test('set float material property (roughness) for custom geometry', @@ -640,11 +498,11 @@ void main() async { // this won't actually do anything because the default ubershader doesn't use specular/glossiness // but we can at least check that the call succeeds - await _capture(viewer, "set_material_roughness_pre"); + await testHelper.capture(viewer, "set_material_roughness_pre"); await viewer.setMaterialPropertyFloat(cube, "metallicFactor", 0, 0.0); await viewer.setMaterialPropertyFloat(cube, "roughnessFactor", 0, 0.0); - await _capture(viewer, "set_material_roughness_post"); + await testHelper.capture(viewer, "set_material_roughness_post"); }); }); @@ -703,7 +561,7 @@ void main() async { // we need an explicit render call here to process the transform queue await viewer.render(); - await _capture(viewer, "set_position_from_viewport_coords"); + await testHelper.capture(viewer, "set_position_from_viewport_coords"); }); }); @@ -714,11 +572,11 @@ void main() async { await viewer .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 8)); await viewer.setCameraPosition(0, 2, 0); - await _capture(viewer, "grid_overlay_default"); + await testHelper.capture(viewer, "grid_overlay_default"); await viewer.setLayerVisibility(7, true); - await _capture(viewer, "grid_overlay_enabled"); + await testHelper.capture(viewer, "grid_overlay_enabled"); await viewer.setLayerVisibility(7, false); - await _capture(viewer, "grid_overlay_disabled"); + await testHelper.capture(viewer, "grid_overlay_disabled"); }); test('load glb from buffer with layer', () async { @@ -731,9 +589,9 @@ void main() async { var buffer = File("$testDir/cube.glb").readAsBytesSync(); var model = await viewer.loadGlbFromBuffer(buffer, layer: 1); - await _capture(viewer, "load_glb_from_buffer_with_layer_disabled"); + await testHelper.capture(viewer, "load_glb_from_buffer_with_layer_disabled"); await viewer.setLayerVisibility(1, true); - await _capture(viewer, "load_glb_from_buffer_with_layer_enabled"); + await testHelper.capture(viewer, "load_glb_from_buffer_with_layer_enabled"); }); test('change layer visibility at runtime', () async { @@ -745,21 +603,21 @@ void main() async { .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5)); var cube = await viewer.createGeometry(GeometryHelper.cube()); - await _capture(viewer, "change_layer_visibility_at_runtime_default"); + await testHelper.capture(viewer, "change_layer_visibility_at_runtime_default"); // all entities set to layer 0 by default, so this should now be invisible await viewer.setLayerVisibility(0, false); - await _capture( + await testHelper.capture( viewer, "change_layer_visibility_at_runtime_layer0_invisible"); // now change the visibility layer to 5, should be invisible await viewer.setVisibilityLayer(cube, 5); - await _capture( + await testHelper.capture( viewer, "change_layer_visibility_at_runtime_layer5_invisible"); // now toggle layer 5 visibility, cube should now be visible await viewer.setLayerVisibility(5, true); - await _capture( + await testHelper.capture( viewer, "change_layer_visibility_at_runtime_layer5_visible"); }); }); @@ -775,7 +633,7 @@ void main() async { // await viewer // .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5)); // await viewer.setRendering(true); - // await _capture(viewer, "point_light"); + // await testHelper.capture(viewer, "point_light"); // await viewer.setRendering(false); // }); @@ -791,7 +649,7 @@ void main() async { // await viewer // .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5)); // await viewer.setRendering(true); - // await _capture(viewer, "move_point_light"); + // await testHelper.capture(viewer, "move_point_light"); // await viewer.setRendering(false); // }); @@ -805,7 +663,7 @@ void main() async { // await viewer // .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5)); // await viewer.setRendering(true); - // await _capture(viewer, "directional_light"); + // await testHelper.capture(viewer, "directional_light"); // await viewer.setRendering(false); // }); @@ -820,7 +678,7 @@ void main() async { // await viewer // .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5)); // await viewer.setRendering(true); - // await _capture(viewer, "set_directional_light_direction"); + // await testHelper.capture(viewer, "set_directional_light_direction"); // await viewer.setRendering(false); // }); @@ -839,7 +697,7 @@ void main() async { await viewer .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), pi / 8)); await viewer.setStencilHighlight(model); - await _capture(viewer, "stencil_highlight_glb"); + await testHelper.capture(viewer, "stencil_highlight_glb"); }); test('set stencil highlight for geometry', () async { @@ -853,11 +711,11 @@ void main() async { var cube = await viewer.createGeometry(GeometryHelper.cube()); await viewer.setStencilHighlight(cube); - await _capture(viewer, "stencil_highlight_geometry"); + await testHelper.capture(viewer, "stencil_highlight_geometry"); await viewer.removeStencilHighlight(cube); - await _capture(viewer, "stencil_highlight_geometry_remove"); + await testHelper.capture(viewer, "stencil_highlight_geometry_remove"); }); test('set stencil highlight for gltf asset', () async { @@ -873,11 +731,11 @@ void main() async { await viewer.setStencilHighlight(cube1); - await _capture(viewer, "stencil_highlight_gltf"); + await testHelper.capture(viewer, "stencil_highlight_gltf"); await viewer.removeStencilHighlight(cube1); - await _capture(viewer, "stencil_highlight_gltf_removed"); + await testHelper.capture(viewer, "stencil_highlight_gltf_removed"); }); test('set stencil highlight for multiple geometry ', () async { @@ -894,12 +752,12 @@ void main() async { await viewer.setStencilHighlight(cube1); await viewer.setStencilHighlight(cube2, r: 0.0, g: 0.0, b: 1.0); - await _capture(viewer, "stencil_highlight_multiple_geometry"); + await testHelper.capture(viewer, "stencil_highlight_multiple_geometry"); await viewer.removeStencilHighlight(cube1); await viewer.removeStencilHighlight(cube2); - await _capture(viewer, "stencil_highlight_multiple_geometry_removed"); + await testHelper.capture(viewer, "stencil_highlight_multiple_geometry_removed"); }); test('set stencil highlight for multiple gltf assets ', () async { @@ -918,12 +776,12 @@ void main() async { await viewer.setStencilHighlight(cube1); await viewer.setStencilHighlight(cube2, r: 0.0, g: 0.0, b: 1.0); - await _capture(viewer, "stencil_highlight_multiple_geometry"); + await testHelper.capture(viewer, "stencil_highlight_multiple_geometry"); await viewer.removeStencilHighlight(cube1); await viewer.removeStencilHighlight(cube2); - await _capture(viewer, "stencil_highlight_multiple_geometry_removed"); + await testHelper.capture(viewer, "stencil_highlight_multiple_geometry_removed"); }); }); @@ -959,7 +817,7 @@ void main() async { await viewer.applyTexture(texture, cube, materialIndex: 0, parameterName: "baseColorMap"); - await _capture(viewer, "texture_applied_to_geometry"); + await testHelper.capture(viewer, "texture_applied_to_geometry"); await viewer.removeEntity(cube); await viewer.destroyTexture(texture); @@ -976,136 +834,136 @@ void main() async { }); }); - group("unproject", () { - test("unproject", () async { - final dimensions = (width: 1280, height: 768); + // group("unproject", () { + // test("unproject", () async { + // final dimensions = (width: 1280, height: 768); - var viewer = await createViewer(viewportDimensions: dimensions); - await viewer.setPostProcessing(false); - // await viewer.setToneMapping(ToneMapper.LINEAR); - await viewer.setBackgroundColor(1.0, 1.0, 1.0, 1.0); - // await viewer.createIbl(1.0, 1.0, 1.0, 100000); - await viewer.addLight(LightType.SUN, 6500, 100000, -2, 0, 0, 1, -1, 0); - await viewer.addLight(LightType.SPOT, 6500, 500000, 0, 0, 2, 0, 0, -1, - falloffRadius: 10, spotLightConeInner: 1.0, spotLightConeOuter: 2.0); + // var viewer = await createViewer(viewportDimensions: dimensions); + // await viewer.setPostProcessing(false); + // // await viewer.setToneMapping(ToneMapper.LINEAR); + // await viewer.setBackgroundColor(1.0, 1.0, 1.0, 1.0); + // // await viewer.createIbl(1.0, 1.0, 1.0, 100000); + // await viewer.addLight(LightType.SUN, 6500, 100000, -2, 0, 0, 1, -1, 0); + // await viewer.addLight(LightType.SPOT, 6500, 500000, 0, 0, 2, 0, 0, -1, + // falloffRadius: 10, spotLightConeInner: 1.0, spotLightConeOuter: 2.0); - await viewer.setCameraPosition(-3, 4, 6); - await viewer.setCameraRotation( - Quaternion.axisAngle(Vector3(0, 1, 0), -pi / 8) * - Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 6)); - var cube = - await viewer.createGeometry(GeometryHelper.cube(), keepData: true); - await viewer.setMaterialPropertyFloat4( - cube, "baseColorFactor", 0, 1.0, 1.0, 1.0, 1.0); - var textureData = - File("$testDir/cube_texture_512x512.png").readAsBytesSync(); - var texture = await viewer.createTexture(textureData); - await viewer.applyTexture(texture, cube, - materialIndex: 0, parameterName: "baseColorMap"); + // await viewer.setCameraPosition(-3, 4, 6); + // await viewer.setCameraRotation( + // Quaternion.axisAngle(Vector3(0, 1, 0), -pi / 8) * + // Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 6)); + // var cube = + // await viewer.createGeometry(GeometryHelper.cube(), keepData: true); + // await viewer.setMaterialPropertyFloat4( + // cube, "baseColorFactor", 0, 1.0, 1.0, 1.0, 1.0); + // var textureData = + // File("$testDir/cube_texture_512x512.png").readAsBytesSync(); + // var texture = await viewer.createTexture(textureData); + // await viewer.applyTexture(texture, cube, + // materialIndex: 0, parameterName: "baseColorMap"); - var numFrames = 60; + // var numFrames = 60; - // first do the render - for (int i = 0; i < numFrames; i++) { - await viewer.setCameraPosition(-3 + (i / numFrames * 2), 4, 6); + // // first do the render + // for (int i = 0; i < numFrames; i++) { + // await viewer.setCameraPosition(-3 + (i / numFrames * 2), 4, 6); - await viewer.setCameraRotation( - Quaternion.axisAngle(Vector3(0, 1, 0), -pi / 8) * - Quaternion.axisAngle( - Vector3(1, 0, 0), -pi / 6 - (i / numFrames * pi / 6))); + // await viewer.setCameraRotation( + // Quaternion.axisAngle(Vector3(0, 1, 0), -pi / 8) * + // Quaternion.axisAngle( + // Vector3(1, 0, 0), -pi / 6 - (i / numFrames * pi / 6))); - var rendered = await _capture(viewer, "unproject_render$i"); - var renderPng = - await pixelsToPng(rendered, dimensions.width, dimensions.height); + // var rendered = await testHelper.capture(viewer, "unproject_render$i"); + // var renderPng = + // await pixelsToPng(rendered, dimensions.width, dimensions.height); - File("${outDir.path}/unproject_render${i}.png") - .writeAsBytesSync(renderPng); - } + // File("${outDir.path}/unproject_render${i}.png") + // .writeAsBytesSync(renderPng); + // } - // then go off and convert the video + // // then go off and convert the video - // now unproject the render back onto the geometry - final textureSize = (width: 1280, height: 768); - var pixels = []; - // note we skip the first frame - for (int i = 0; i < numFrames; i++) { - await viewer.setCameraPosition(-3 + (i / numFrames * 2), 4, 6); + // // now unproject the render back onto the geometry + // final textureSize = (width: 1280, height: 768); + // var pixels = []; + // // note we skip the first frame + // for (int i = 0; i < numFrames; i++) { + // await viewer.setCameraPosition(-3 + (i / numFrames * 2), 4, 6); - await viewer.setCameraRotation( - Quaternion.axisAngle(Vector3(0, 1, 0), -pi / 8) * - Quaternion.axisAngle( - Vector3(1, 0, 0), -pi / 6 - (i / numFrames * pi / 6))); + // await viewer.setCameraRotation( + // Quaternion.axisAngle(Vector3(0, 1, 0), -pi / 8) * + // Quaternion.axisAngle( + // Vector3(1, 0, 0), -pi / 6 - (i / numFrames * pi / 6))); - var input = pngToPixelBuffer(File( - "${outDir.path}/a8c317af-6081-4848-8a06-f6b69bc57664_${i + 1}.png") - .readAsBytesSync()); - var pixelBuffer = await (await viewer as ThermionViewerFFI).unproject( - cube, - input, - dimensions.width, - dimensions.height, - textureSize.width, - textureSize.height); + // var input = pngToPixelBuffer(File( + // "${outDir.path}/a8c317af-6081-4848-8a06-f6b69bc57664_${i + 1}.png") + // .readAsBytesSync()); + // var pixelBuffer = await (await viewer as ThermionViewerFFI).unproject( + // cube, + // input, + // dimensions.width, + // dimensions.height, + // textureSize.width, + // textureSize.height); - // var png = await pixelsToPng(Uint8List.fromList(pixelBuffer), - // dimensions.width, dimensions.height); + // // var png = await pixelsToPng(Uint8List.fromList(pixelBuffer), + // // dimensions.width, dimensions.height); - await savePixelBufferToBmp( - pixelBuffer, - textureSize.width, - textureSize.height, - p.join(outDir.path, "unprojected_texture${i}.bmp")); + // await savePixelBufferToBmp( + // pixelBuffer, + // textureSize.width, + // textureSize.height, + // p.join(outDir.path, "unprojected_texture${i}.bmp")); - pixels.add(pixelBuffer); + // pixels.add(pixelBuffer); - if (i > 10) { - break; - } - } + // if (i > 10) { + // break; + // } + // } - // } + // // } - final aggregatePixelBuffer = medianImages(pixels); - await savePixelBufferToBmp(aggregatePixelBuffer, textureSize.width, - textureSize.height, "unproject_texture.bmp"); - var pixelBufferPng = await pixelsToPng( - Uint8List.fromList(aggregatePixelBuffer), - dimensions.width, - dimensions.height); - File("${outDir.path}/unproject_texture.png") - .writeAsBytesSync(pixelBufferPng); + // final aggregatePixelBuffer = medianImages(pixels); + // await savePixelBufferToBmp(aggregatePixelBuffer, textureSize.width, + // textureSize.height, "unproject_texture.bmp"); + // var pixelBufferPng = await pixelsToPng( + // Uint8List.fromList(aggregatePixelBuffer), + // dimensions.width, + // dimensions.height); + // File("${outDir.path}/unproject_texture.png") + // .writeAsBytesSync(pixelBufferPng); - await viewer.setPostProcessing(true); - await viewer.setToneMapping(ToneMapper.LINEAR); + // await viewer.setPostProcessing(true); + // await viewer.setToneMapping(ToneMapper.LINEAR); - final unlit = await viewer.createUnlitMaterialInstance(); - await viewer.removeEntity(cube); - cube = await viewer.createGeometry(GeometryHelper.cube(), - materialInstance: unlit); - var reconstructedTexture = await viewer.createTexture(pixelBufferPng); - await viewer.applyTexture(reconstructedTexture, cube); + // final unlit = await viewer.createUnlitMaterialInstance(); + // await viewer.removeEntity(cube); + // cube = await viewer.createGeometry(GeometryHelper.cube(), + // materialInstance: unlit); + // var reconstructedTexture = await viewer.createTexture(pixelBufferPng); + // await viewer.applyTexture(reconstructedTexture, cube); - await viewer.setCameraRotation( - Quaternion.axisAngle(Vector3(0, 1, 0), -pi / 8) * - Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 6)); - await _capture(viewer, "unproject_reconstruct"); + // await viewer.setCameraRotation( + // Quaternion.axisAngle(Vector3(0, 1, 0), -pi / 8) * + // Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 6)); + // await testHelper.capture(viewer, "unproject_reconstruct"); - // now re-render - for (int i = 0; i < numFrames; i++) { - await viewer.setCameraPosition(-3 + (i / numFrames * 2), 4, 6); + // // now re-render + // for (int i = 0; i < numFrames; i++) { + // await viewer.setCameraPosition(-3 + (i / numFrames * 2), 4, 6); - await viewer.setCameraRotation( - Quaternion.axisAngle(Vector3(0, 1, 0), -pi / 8) * - Quaternion.axisAngle( - Vector3(1, 0, 0), -pi / 6 - (i / numFrames * pi / 6))); + // await viewer.setCameraRotation( + // Quaternion.axisAngle(Vector3(0, 1, 0), -pi / 8) * + // Quaternion.axisAngle( + // Vector3(1, 0, 0), -pi / 6 - (i / numFrames * pi / 6))); - var rendered = await _capture(viewer, "unproject_rerender$i"); - var renderPng = - await pixelsToPng(rendered, dimensions.width, dimensions.height); + // var rendered = await testHelper.capture(viewer, "unproject_rerender$i"); + // var renderPng = + // await pixelsToPng(rendered, dimensions.width, dimensions.height); - File("${outDir.path}/unproject_rerender${i}.png") - .writeAsBytesSync(renderPng); - } - }, timeout: Timeout(Duration(minutes: 2))); - }); + // File("${outDir.path}/unproject_rerender${i}.png") + // .writeAsBytesSync(renderPng); + // } + // }, timeout: Timeout(Duration(minutes: 2))); + // }); } diff --git a/thermion_flutter/thermion_flutter/analysis_options.yaml b/thermion_flutter/thermion_flutter/analysis_options.yaml index a5744c1c..99968f0c 100644 --- a/thermion_flutter/thermion_flutter/analysis_options.yaml +++ b/thermion_flutter/thermion_flutter/analysis_options.yaml @@ -1,4 +1,7 @@ include: package:flutter_lints/flutter.yaml +analyzer: + errors: + constant_identifier_names: ignore # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options diff --git a/thermion_flutter/thermion_flutter/lib/src/gestures/default_keyboard_camera_flight_delegate.dart b/thermion_flutter/thermion_flutter/lib/src/gestures/default_keyboard_camera_flight_delegate.dart deleted file mode 100644 index 72213fe2..00000000 --- a/thermion_flutter/thermion_flutter/lib/src/gestures/default_keyboard_camera_flight_delegate.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'dart:async'; -import 'dart:ui'; - -import 'package:flutter/services.dart'; -import 'package:thermion_dart/thermion_dart/thermion_viewer.dart'; -import 'package:thermion_flutter/src/gestures/delegates.dart'; -import 'package:vector_math/vector_math_64.dart'; - -class DefaultKeyboardCameraFlightDelegate - { - final ThermionViewer viewer; - - static const double _panSensitivity = 0.005; - static const double _keyMoveSensitivity = 0.1; - - final Map _pressedKeys = {}; - Timer? _moveTimer; - - DefaultKeyboardCameraFlightDelegate(this.viewer) { - _startMoveLoop(); - } - - @override - Future panCamera(Offset delta, Vector2? velocity) async { - double deltaX = delta.dx; - double deltaY = delta.dy; - deltaX *= _panSensitivity * viewer.pixelRatio; - deltaY *= _panSensitivity * viewer.pixelRatio; - - await _moveCamera(deltaX, deltaY, 0); - } - - @override - Future onKeypress(PhysicalKeyboardKey key) async { - _pressedKeys[key] = true; - } - - // New method to handle key release - Future onKeyRelease(PhysicalKeyboardKey key) async { - _pressedKeys.remove(key); - } - - void _startMoveLoop() { - _moveTimer = Timer.periodic( - Duration(milliseconds: 16), (_) => _processKeyboardInput()); - } - - Future _processKeyboardInput() async { - double dx = 0, dy = 0, dz = 0; - - if (_pressedKeys[PhysicalKeyboardKey.keyW] == true) - dz += _keyMoveSensitivity; - if (_pressedKeys[PhysicalKeyboardKey.keyS] == true) - dz -= _keyMoveSensitivity; - if (_pressedKeys[PhysicalKeyboardKey.keyA] == true) - dx -= _keyMoveSensitivity; - if (_pressedKeys[PhysicalKeyboardKey.keyD] == true) - dx += _keyMoveSensitivity; - - if (dx != 0 || dy != 0 || dz != 0) { - await _moveCamera(dx, dy, dz); - } - // Removed _pressedKeys.clear(); from here - } - - Future _moveCamera(double dx, double dy, double dz) async { - Matrix4 currentModelMatrix = await viewer.getCameraModelMatrix(); - Vector3 currentPosition = currentModelMatrix.getTranslation(); - Quaternion currentRotation = - Quaternion.fromRotation(currentModelMatrix.getRotation()); - - Vector3 forward = Vector3(0, 0, -1)..applyQuaternion(currentRotation); - Vector3 right = Vector3(1, 0, 0)..applyQuaternion(currentRotation); - Vector3 up = Vector3(0, 1, 0)..applyQuaternion(currentRotation); - - Vector3 moveOffset = right * dx + up * dy + forward * dz; - Vector3 newPosition = currentPosition + moveOffset; - - Matrix4 newModelMatrix = - Matrix4.compose(newPosition, currentRotation, Vector3(1, 1, 1)); - await viewer.setCameraModelMatrix4(newModelMatrix); - } - - void dispose() { - _moveTimer?.cancel(); - } -} \ No newline at end of file diff --git a/thermion_flutter/thermion_flutter/lib/src/gestures/default_pick_delegate.dart b/thermion_flutter/thermion_flutter/lib/src/gestures/default_pick_delegate.dart deleted file mode 100644 index f43ed86b..00000000 --- a/thermion_flutter/thermion_flutter/lib/src/gestures/default_pick_delegate.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'dart:ui'; - -import 'package:thermion_dart/thermion_dart/thermion_viewer.dart'; -import 'package:thermion_flutter/src/gestures/delegates.dart'; - -class DefaultPickDelegate extends PickDelegate { - final ThermionViewer _viewer; - - const DefaultPickDelegate(this._viewer); - - @override - void pick(Offset location) { - _viewer.pick(location.dx.toInt(), location.dy.toInt()); - } -} diff --git a/thermion_flutter/thermion_flutter/lib/src/gestures/default_velocity_delegate.dart b/thermion_flutter/thermion_flutter/lib/src/gestures/default_velocity_delegate.dart deleted file mode 100644 index b6c096eb..00000000 --- a/thermion_flutter/thermion_flutter/lib/src/gestures/default_velocity_delegate.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'dart:async'; -import 'dart:ui'; - -import 'package:thermion_flutter/src/gestures/delegates.dart'; -import 'package:vector_math/vector_math_64.dart'; - -class DefaultVelocityDelegate extends VelocityDelegate { - Vector2? _velocity; - Timer? _decelerationTimer; - final double _decelerationFactor = 0.95; - final double _minVelocity = 0.01; - - Vector2? get velocity => _velocity; - - @override - void updateVelocity(Offset delta) { - _velocity = Vector2(delta.dx, delta.dy); - } - - @override - void startDeceleration() { - if (_velocity != null && _velocity!.length > _minVelocity) { - _decelerationTimer = Timer.periodic(Duration(milliseconds: 16), (timer) { - if (_velocity == null || _velocity!.length <= _minVelocity) { - stopDeceleration(); - return; - } - - _velocity = _velocity! * _decelerationFactor; - - }); - } - } - - @override - void stopDeceleration() { - _decelerationTimer?.cancel(); - _decelerationTimer = null; - _velocity = null; - } - - @override - void dispose() { - stopDeceleration(); - } -} diff --git a/thermion_flutter/thermion_flutter/lib/src/gestures/default_zoom_camera_delegate.dart b/thermion_flutter/thermion_flutter/lib/src/gestures/default_zoom_camera_delegate.dart deleted file mode 100644 index 7d453d8b..00000000 --- a/thermion_flutter/thermion_flutter/lib/src/gestures/default_zoom_camera_delegate.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/widgets.dart'; -import 'package:thermion_dart/thermion_dart/thermion_viewer.dart'; -import 'package:thermion_flutter/src/gestures/delegates.dart'; -import 'package:vector_math/vector_math_64.dart'; - -class DefaultZoomCameraDelegate { - final ThermionViewer viewer; - final double zoomSensitivity; - - final double? Function(Vector3 cameraPosition)? getDistanceToTarget; - - DefaultZoomCameraDelegate(this.viewer, - {this.zoomSensitivity = 0.005, this.getDistanceToTarget}); - - /// - /// Converts the given [scrollDelta] (usually somewhere between 1 and -1) to - /// a percentage of the current camera distance (either to the origin, - /// or to a custom target) along its forward vector. - /// In other words, "shift " - /// - double calculateZoomFactor( - double scrollDelta, Vector2? velocity) { - double zoomFactor = scrollDelta * zoomSensitivity; - if (zoomFactor.abs() < 0.0001) { - zoomFactor = scrollDelta * zoomSensitivity; - } - return zoomFactor; - } - - @override - Future zoom(double scrollDelta, Vector2? velocity) async { - Matrix4 currentModelMatrix = await viewer.getCameraModelMatrix(); - final cameraRotation = currentModelMatrix.getRotation(); - final cameraPosition = currentModelMatrix.getTranslation(); - - Vector3 forwardVector = cameraRotation.getColumn(2); - forwardVector.normalize(); - - var zoomDistance = - calculateZoomFactor(scrollDelta, velocity); - - Vector3 newPosition = cameraPosition + (forwardVector * zoomDistance); - await viewer.setCameraPosition(newPosition.x, newPosition.y, newPosition.z); - } -} diff --git a/thermion_flutter/thermion_flutter/lib/src/gestures/delegate_gesture_handler.dart b/thermion_flutter/thermion_flutter/lib/src/gestures/delegate_gesture_handler.dart deleted file mode 100644 index 76c0cf8a..00000000 --- a/thermion_flutter/thermion_flutter/lib/src/gestures/delegate_gesture_handler.dart +++ /dev/null @@ -1,245 +0,0 @@ -import 'dart:async'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:flutter/services.dart'; -import 'package:logging/logging.dart'; -import 'package:thermion_flutter/src/gestures/default_pick_delegate.dart'; -import 'package:thermion_flutter/src/gestures/default_velocity_delegate.dart'; -import 'package:thermion_flutter/src/gestures/delegates.dart'; -import 'package:thermion_flutter/src/gestures/fixed_orbit_camera_rotation_delegate.dart'; -import 'package:thermion_flutter/src/gestures/free_flight_camera_delegate.dart'; -import 'package:thermion_flutter/thermion_flutter.dart'; -import 'package:vector_math/vector_math_64.dart'; - -class DelegateGestureHandler implements ThermionGestureHandler { - final ThermionViewer viewer; - final Logger _logger = Logger("CustomGestureHandler"); - - CameraDelegate? cameraDelegate; - VelocityDelegate? velocityDelegate; - PickDelegate? pickDelegate; - - Ticker? _ticker; - - Map _accumulatedDeltas = {}; - double _accumulatedScrollDelta = 0.0; - int _activePointers = 0; - bool _isMiddleMouseButtonPressed = false; - - VoidCallback? _keyboardListenerDisposer; - - final Map _actions = { - GestureType.LMB_HOLD_AND_MOVE: GestureAction.PAN_CAMERA, - GestureType.MMB_HOLD_AND_MOVE: GestureAction.ROTATE_CAMERA, - GestureType.SCROLLWHEEL: GestureAction.ZOOM_CAMERA, - GestureType.POINTER_MOVE: GestureAction.NONE, - }; - - DelegateGestureHandler({ - required this.viewer, - required this.cameraDelegate, - required this.velocityDelegate, - this.pickDelegate, - Map? actions, - }) { - _initializeKeyboardListener(); - if (actions != null) { - _actions.addAll(actions); - } - _initializeAccumulatedDeltas(); - } - - factory DelegateGestureHandler.fixedOrbit(ThermionViewer viewer, - {double minimumDistance = 10.0, - double? Function(Vector3)? getDistanceToTarget, - PickDelegate? pickDelegate}) => - DelegateGestureHandler( - viewer: viewer, - pickDelegate: pickDelegate, - cameraDelegate: FixedOrbitRotateCameraDelegate(viewer, - getDistanceToTarget: getDistanceToTarget, - minimumDistance: minimumDistance), - velocityDelegate: DefaultVelocityDelegate(), - actions: {GestureType.MMB_HOLD_AND_MOVE:GestureAction.ROTATE_CAMERA} - ); - - factory DelegateGestureHandler.flight(ThermionViewer viewer, - {PickDelegate? pickDelegate}) => - DelegateGestureHandler( - viewer: viewer, - pickDelegate: pickDelegate, - cameraDelegate: FreeFlightCameraDelegate(viewer), - velocityDelegate: DefaultVelocityDelegate(), - actions: {GestureType.POINTER_MOVE: GestureAction.ROTATE_CAMERA}, - ); - - void _initializeAccumulatedDeltas() { - for (var gestureType in GestureType.values) { - _accumulatedDeltas[gestureType] = Offset.zero; - } - } - - Future _applyAccumulatedUpdates() async { - for (var gestureType in GestureType.values) { - Offset delta = _accumulatedDeltas[gestureType] ?? Offset.zero; - if (delta != Offset.zero) { - velocityDelegate?.updateVelocity(delta); - - var action = _actions[gestureType]; - switch (action) { - case GestureAction.PAN_CAMERA: - await cameraDelegate?.pan(delta, velocityDelegate?.velocity); - break; - case GestureAction.ROTATE_CAMERA: - await cameraDelegate?.rotate(delta, velocityDelegate?.velocity); - break; - case GestureAction.NONE: - // Do nothing - break; - default: - _logger.warning( - "Unsupported gesture action: $action for type: $gestureType"); - break; - } - - _accumulatedDeltas[gestureType] = Offset.zero; - } - } - - if (_accumulatedScrollDelta != 0.0) { - await cameraDelegate?.zoom( - _accumulatedScrollDelta, velocityDelegate?.velocity); - _accumulatedScrollDelta = 0.0; - } - } - - @override - Future onPointerDown(Offset localPosition, int buttons) async { - velocityDelegate?.stopDeceleration(); - _activePointers++; - if (buttons & kMiddleMouseButton != 0) { - _isMiddleMouseButtonPressed = true; - } - if (buttons & kPrimaryButton != 0) { - final action = _actions[GestureType.LMB_DOWN]; - switch (action) { - case GestureAction.PICK_ENTITY: - pickDelegate?.pick(localPosition); - default: - // noop - } - } - await _applyAccumulatedUpdates(); - } - - @override - Future onPointerMove( - Offset localPosition, Offset delta, int buttons) async { - GestureType gestureType = _getGestureTypeFromButtons(buttons); - if (gestureType == GestureType.MMB_HOLD_AND_MOVE || - (_actions[GestureType.POINTER_MOVE] == GestureAction.ROTATE_CAMERA && - gestureType == GestureType.POINTER_MOVE)) { - _accumulatedDeltas[GestureType.MMB_HOLD_AND_MOVE] = - (_accumulatedDeltas[GestureType.MMB_HOLD_AND_MOVE] ?? Offset.zero) + - delta; - } else { - _accumulatedDeltas[gestureType] = - (_accumulatedDeltas[gestureType] ?? Offset.zero) + delta; - } - await _applyAccumulatedUpdates(); - } - - @override - Future onPointerUp(int buttons) async { - _activePointers--; - if (_activePointers == 0) { - velocityDelegate?.startDeceleration(); - } - if (buttons & kMiddleMouseButton != 0) { - _isMiddleMouseButtonPressed = false; - } - } - - GestureType _getGestureTypeFromButtons(int buttons) { - if (buttons & kPrimaryMouseButton != 0) { - return GestureType.LMB_HOLD_AND_MOVE; - } - if (buttons & kMiddleMouseButton != 0 || _isMiddleMouseButtonPressed) { - return GestureType.MMB_HOLD_AND_MOVE; - } - return GestureType.POINTER_MOVE; - } - - @override - Future onPointerHover(Offset localPosition, Offset delta) async { - if (_actions[GestureType.POINTER_MOVE] == GestureAction.ROTATE_CAMERA) { - _accumulatedDeltas[GestureType.POINTER_MOVE] = - (_accumulatedDeltas[GestureType.POINTER_MOVE] ?? Offset.zero) + delta; - } - } - - @override - Future onPointerScroll(Offset localPosition, double scrollDelta) async { - if (_actions[GestureType.SCROLLWHEEL] != GestureAction.ZOOM_CAMERA) { - throw Exception( - "Unsupported action: ${_actions[GestureType.SCROLLWHEEL]}"); - } - - try { - _accumulatedScrollDelta += scrollDelta; - } catch (e) { - _logger.warning("Error during scroll accumulation: $e"); - } - await _applyAccumulatedUpdates(); - } - - @override - void dispose() { - velocityDelegate?.dispose(); - _keyboardListenerDisposer?.call(); - _ticker?.dispose(); - } - - @override - Future get initialized => viewer.initialized; - - @override - Future onScaleEnd() async {} - - @override - Future onScaleStart() async {} - - @override - Future onScaleUpdate() async {} - - @override - void setActionForType(GestureType gestureType, GestureAction gestureAction) { - _actions[gestureType] = gestureAction; - } - - @override - GestureAction? getActionForType(GestureType gestureType) { - return _actions[gestureType]; - } - - void _initializeKeyboardListener() { - HardwareKeyboard.instance.addHandler(_handleKeyEvent); - _keyboardListenerDisposer = () { - HardwareKeyboard.instance.removeHandler(_handleKeyEvent); - }; - } - - bool _handleKeyEvent(KeyEvent event) { - if (_actions[GestureType.KEYDOWN] == GestureAction.NONE) { - return false; - } - if (event is KeyDownEvent || event is KeyRepeatEvent) { - cameraDelegate?.onKeypress(event.physicalKey); - return true; - } else if (event is KeyUpEvent) { - cameraDelegate?.onKeyRelease(event.physicalKey); - return true; - } - return false; - } -} diff --git a/thermion_flutter/thermion_flutter/lib/src/gestures/delegates.dart b/thermion_flutter/thermion_flutter/lib/src/gestures/delegates.dart deleted file mode 100644 index e90eedb7..00000000 --- a/thermion_flutter/thermion_flutter/lib/src/gestures/delegates.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'dart:ui'; -import 'package:flutter/services.dart'; -import 'package:vector_math/vector_math_64.dart'; - -abstract class CameraDelegate { - Future rotate(Offset delta, Vector2? velocity); - Future pan(Offset delta, Vector2? velocity); - Future zoom(double yScrollDeltaInPixels, Vector2? velocity); - Future onKeypress(PhysicalKeyboardKey key); - Future onKeyRelease(PhysicalKeyboardKey key); -} - -abstract class VelocityDelegate { - Vector2? get velocity; - - void updateVelocity(Offset delta); - - void startDeceleration(); - - void stopDeceleration(); - - void dispose() { - stopDeceleration(); - } -} - -abstract class PickDelegate { - const PickDelegate(); - void pick(Offset location); -} diff --git a/thermion_flutter/thermion_flutter/lib/src/gestures/fixed_orbit_camera_rotation_delegate.dart b/thermion_flutter/thermion_flutter/lib/src/gestures/fixed_orbit_camera_rotation_delegate.dart deleted file mode 100644 index ed2bd753..00000000 --- a/thermion_flutter/thermion_flutter/lib/src/gestures/fixed_orbit_camera_rotation_delegate.dart +++ /dev/null @@ -1,150 +0,0 @@ -import 'dart:async'; -import 'dart:math'; -import 'package:flutter/services.dart'; -import 'package:thermion_dart/thermion_dart/thermion_viewer.dart'; -import 'package:thermion_flutter/src/gestures/delegates.dart'; -import 'package:vector_math/vector_math_64.dart'; - -/// A camera delegate that rotates the camera around the origin. -/// Panning is not permitted; zooming is permitted (up to a minimum distance) -/// -/// The rotation sensitivity will be automatically adjusted so that -/// 100 horizontal pixels equates to a geodetic distance of 1m when the camera -/// is 1m from the surface (denoted by distanceToSurface). This scales to 10m -/// geodetic distance when the camera is 100m from the surface, 100m when the -/// camera is 1000m from the surface, and so on. -/// -/// -class FixedOrbitRotateCameraDelegate implements CameraDelegate { - final ThermionViewer viewer; - final double minimumDistance; - double? Function(Vector3)? getDistanceToTarget; - - Offset _accumulatedRotationDelta = Offset.zero; - double _accumulatedZoomDelta = 0.0; - - static final _up = Vector3(0, 1, 0); - Timer? _updateTimer; - - FixedOrbitRotateCameraDelegate( - this.viewer, { - this.getDistanceToTarget, - this.minimumDistance = 10.0, - }); - - void dispose() { - _updateTimer?.cancel(); - } - - @override - Future rotate(Offset delta, Vector2? velocity) async { - _accumulatedRotationDelta += delta; - await _applyAccumulatedUpdates(); - } - - @override - Future pan(Offset delta, Vector2? velocity) { - throw UnimplementedError("Not supported in fixed orbit mode"); - } - - @override - Future zoom(double yScrollDeltaInPixels, Vector2? velocity) async { - _accumulatedZoomDelta += yScrollDeltaInPixels > 0 ? 1 : -1; - await _applyAccumulatedUpdates(); - } - - Future _applyAccumulatedUpdates() async { - if (_accumulatedRotationDelta.distanceSquared == 0.0 && - _accumulatedZoomDelta == 0.0) { - return; - } - - var viewMatrix = await viewer.getCameraViewMatrix(); - var modelMatrix = await viewer.getCameraModelMatrix(); - var projectionMatrix = await viewer.getCameraProjectionMatrix(); - var inverseProjectionMatrix = projectionMatrix.clone()..invert(); - Vector3 currentPosition = modelMatrix.getTranslation(); - - Vector3 forward = -currentPosition.normalized(); - Vector3 right = _up.cross(forward).normalized(); - Vector3 up = forward.cross(right); - - // first, we find the point in the sphere that intersects with the camera - // forward vector - double radius = 0.0; - double? distanceToTarget = getDistanceToTarget?.call(currentPosition); - if (distanceToTarget != null) { - radius = currentPosition.length - distanceToTarget; - } else { - radius = 1.0; - } - Vector3 intersection = (-forward).scaled(radius); - - // next, calculate the depth value at that intersection point - final intersectionInViewSpace = viewMatrix * - Vector4(intersection.x, intersection.y, intersection.z, 1.0); - final intersectionInClipSpace = projectionMatrix * intersectionInViewSpace; - final intersectionInNdcSpace = - intersectionInClipSpace / intersectionInClipSpace.w; - - // using that depth value, find the world space position of the mouse - // note we flip the signs of the X and Y values - - final ndcX = 2 * - ((-_accumulatedRotationDelta.dx * viewer.pixelRatio) / - viewer.viewportDimensions.$1); - final ndcY = 2 * - ((_accumulatedRotationDelta.dy * viewer.pixelRatio) / - viewer.viewportDimensions.$2); - final ndc = Vector4(ndcX, ndcY, intersectionInNdcSpace.z, 1.0); - - var clipSpace = Vector4( - ndc.x * intersectionInClipSpace.w, - ndcY * intersectionInClipSpace.w, - ndc.z * intersectionInClipSpace.w, - intersectionInClipSpace.w); - Vector4 cameraSpace = inverseProjectionMatrix * clipSpace; - Vector4 worldSpace = modelMatrix * cameraSpace; - - // the new camera world space position will be that position, - // scaled to the camera's current distance - var worldSpace3 = worldSpace.xyz.normalized() * currentPosition.length; - currentPosition = worldSpace3; - - // Apply zoom - if (_accumulatedZoomDelta != 0.0) { - // double zoomFactor = 1.0 + (); - Vector3 toSurface = currentPosition - intersection; - currentPosition = currentPosition + toSurface.scaled(_accumulatedZoomDelta * 0.1); - _accumulatedZoomDelta = 0.0; - } - - // Ensure minimum distance - if (currentPosition.length < radius + minimumDistance) { - currentPosition = - (currentPosition.normalized() * (radius + minimumDistance)); - } - - // Calculate view matrix - forward = -currentPosition.normalized(); - right = _up.cross(forward).normalized(); - up = forward.cross(right); - - Matrix4 newViewMatrix = makeViewMatrix(currentPosition, Vector3.zero(), up); - newViewMatrix.invert(); - - // Set the camera model matrix - await viewer.setCameraModelMatrix4(newViewMatrix); - _accumulatedRotationDelta = Offset.zero; - } - - @override - Future onKeyRelease(PhysicalKeyboardKey key) async { - // Ignore - } - - @override - Future onKeypress(PhysicalKeyboardKey key) async { - // Ignore - } -} diff --git a/thermion_flutter/thermion_flutter/lib/src/gestures/free_flight_camera_delegate.dart b/thermion_flutter/thermion_flutter/lib/src/gestures/free_flight_camera_delegate.dart deleted file mode 100644 index 595f48e6..00000000 --- a/thermion_flutter/thermion_flutter/lib/src/gestures/free_flight_camera_delegate.dart +++ /dev/null @@ -1,206 +0,0 @@ -import 'dart:async'; -import 'dart:ui'; - -import 'package:flutter/scheduler.dart'; -import 'package:flutter/services.dart'; -import 'package:thermion_dart/thermion_dart/thermion_viewer.dart'; -import 'package:thermion_flutter/src/gestures/delegates.dart'; -import 'package:vector_math/vector_math_64.dart'; - -class FreeFlightCameraDelegate implements CameraDelegate { - final ThermionViewer viewer; - - final Vector3? minBounds; - final Vector3? maxBounds; - - final double rotationSensitivity; - final double movementSensitivity; - final double zoomSensitivity; - final double panSensitivity; - final double keyMoveSensitivity; - - static final _up = Vector3(0, 1, 0); - static final _forward = Vector3(0, 0, -1); - static final Vector3 _right = Vector3(1, 0, 0); - - Offset _accumulatedRotation = Offset.zero; - Offset _accumulatedPan = Offset.zero; - double _accumulatedZoom = 0.0; - Vector2? _lastVelocity; - - Ticker? _ticker; - Timer? _moveTimer; - final Map _pressedKeys = {}; - - FreeFlightCameraDelegate( - this.viewer, { - this.minBounds, - this.maxBounds, - this.rotationSensitivity = 0.001, - this.movementSensitivity = 0.1, - this.zoomSensitivity = 0.1, - this.panSensitivity = 0.01, - this.keyMoveSensitivity = 0.1, - }) { - _initializeTicker(); - _startMoveLoop(); - } - - void _initializeTicker() { - _ticker = Ticker(_onTick); - _ticker!.start(); - } - - void _startMoveLoop() { - _moveTimer = Timer.periodic( - Duration(milliseconds: 16), (_) => _processKeyboardInput()); - } - - void _onTick(Duration elapsed) { - _applyAccumulatedUpdates(); - } - - Future _applyAccumulatedUpdates() async { - if (_accumulatedRotation != Offset.zero || - _accumulatedPan != Offset.zero || - _accumulatedZoom != 0.0) { - Matrix4 currentModelMatrix = await viewer.getCameraModelMatrix(); - Vector3 currentPosition = currentModelMatrix.getTranslation(); - Quaternion currentRotation = - Quaternion.fromRotation(currentModelMatrix.getRotation()); - - // Apply rotation - if (_accumulatedRotation != Offset.zero) { - double deltaX = _accumulatedRotation.dx * rotationSensitivity * viewer.pixelRatio; - double deltaY = _accumulatedRotation.dy * rotationSensitivity * viewer.pixelRatio; - double deltaZ = (_accumulatedRotation.dx + _accumulatedRotation.dy) * rotationSensitivity * 0.5 * viewer.pixelRatio; - - Quaternion yawRotation = Quaternion.axisAngle(_up, -deltaX); - Quaternion pitchRotation = Quaternion.axisAngle(_right, -deltaY); - Quaternion rollRotation = Quaternion.axisAngle(_forward, deltaZ); - - currentRotation = currentRotation * rollRotation * pitchRotation * yawRotation ; - currentRotation.normalize(); - - _accumulatedRotation = Offset.zero; - } - - // Apply pan - if (_accumulatedPan != Offset.zero) { - Vector3 right = _right.clone()..applyQuaternion(currentRotation); - Vector3 up = _up.clone()..applyQuaternion(currentRotation); - - double deltaX = _accumulatedPan.dx * panSensitivity * viewer.pixelRatio; - double deltaY = _accumulatedPan.dy * panSensitivity * viewer.pixelRatio; - - Vector3 newPosition = currentPosition + right * -deltaX + up * deltaY; - newPosition = _constrainPosition(newPosition); - - currentPosition = newPosition; - - _accumulatedPan = Offset.zero; - } - - // Apply zoom - if (_accumulatedZoom != 0.0) { - Vector3 forward = _forward.clone()..applyQuaternion(currentRotation); - Vector3 newPosition = currentPosition + forward * _accumulatedZoom * zoomSensitivity; - newPosition = _constrainPosition(newPosition); - - currentPosition = newPosition; - _accumulatedZoom = 0.0; - } - - Matrix4 newModelMatrix = - Matrix4.compose(currentPosition, currentRotation, Vector3(1, 1, 1)); - await viewer.setCameraModelMatrix4(newModelMatrix); - } - } - - Vector3 _constrainPosition(Vector3 position) { - if (minBounds != null) { - position.x = position.x.clamp(minBounds!.x, double.infinity); - position.y = position.y.clamp(minBounds!.y, double.infinity); - position.z = position.z.clamp(minBounds!.z, double.infinity); - } - if (maxBounds != null) { - position.x = position.x.clamp(double.negativeInfinity, maxBounds!.x); - position.y = position.y.clamp(double.negativeInfinity, maxBounds!.y); - position.z = position.z.clamp(double.negativeInfinity, maxBounds!.z); - } - return position; - } - - @override - Future rotate(Offset delta, Vector2? velocity) async { - _accumulatedRotation += delta; - _lastVelocity = velocity; - } - - @override - Future pan(Offset delta, Vector2? velocity) async { - _accumulatedPan += delta; - _lastVelocity = velocity; - } - - @override - Future zoom(double scrollDelta, Vector2? velocity) async { - _accumulatedZoom -= scrollDelta; - _lastVelocity = velocity; - } - - @override - Future onKeypress(PhysicalKeyboardKey key) async { - _pressedKeys[key] = true; - } - - @override - Future onKeyRelease(PhysicalKeyboardKey key) async { - _pressedKeys.remove(key); - } - - Future _processKeyboardInput() async { - double dx = 0, dy = 0, dz = 0; - - if (_pressedKeys[PhysicalKeyboardKey.keyW] == true) { - dz += keyMoveSensitivity; - } - if (_pressedKeys[PhysicalKeyboardKey.keyS] == true) { - dz -= keyMoveSensitivity; - } - if (_pressedKeys[PhysicalKeyboardKey.keyA] == true) { - dx -= keyMoveSensitivity; - } - if (_pressedKeys[PhysicalKeyboardKey.keyD] == true) { - dx += keyMoveSensitivity; - } - - if (dx != 0 || dy != 0 || dz != 0) { - await _moveCamera(dx, dy, dz); - } - } - - Future _moveCamera(double dx, double dy, double dz) async { - Matrix4 currentModelMatrix = await viewer.getCameraModelMatrix(); - Vector3 currentPosition = currentModelMatrix.getTranslation(); - Quaternion currentRotation = - Quaternion.fromRotation(currentModelMatrix.getRotation()); - - Vector3 forward = Vector3(0, 0, -1)..applyQuaternion(currentRotation); - Vector3 right = Vector3(1, 0, 0)..applyQuaternion(currentRotation); - Vector3 up = Vector3(0, 1, 0)..applyQuaternion(currentRotation); - - Vector3 moveOffset = right * dx + up * dy + forward * dz; - Vector3 newPosition = currentPosition + moveOffset; - newPosition = _constrainPosition(newPosition); - - Matrix4 newModelMatrix = - Matrix4.compose(newPosition, currentRotation, Vector3(1, 1, 1)); - await viewer.setCameraModelMatrix4(newModelMatrix); - } - - void dispose() { - _ticker?.dispose(); - _moveTimer?.cancel(); - } -} \ No newline at end of file diff --git a/thermion_flutter/thermion_flutter/lib/src/gestures/picking_camera_gesture_handler.dart b/thermion_flutter/thermion_flutter/lib/src/gestures/picking_camera_gesture_handler.dart deleted file mode 100644 index 4cb461da..00000000 --- a/thermion_flutter/thermion_flutter/lib/src/gestures/picking_camera_gesture_handler.dart +++ /dev/null @@ -1,231 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/gestures.dart'; -import 'package:flutter/services.dart'; -import 'package:logging/logging.dart'; -import 'package:thermion_dart/thermion_dart/entities/abstract_gizmo.dart'; -import 'package:thermion_dart/thermion_dart/thermion_viewer.dart'; -import 'dart:ui'; -import 'package:thermion_flutter/src/widgets/camera/gestures/thermion_gesture_handler.dart'; - -// Renamed implementation -class PickingCameraGestureHandler implements ThermionGestureHandler { - final ThermionViewer viewer; - final bool enableCamera; - final bool enablePicking; - final Logger _logger = Logger("PickingCameraGestureHandler"); - - ThermionGestureState _currentState = ThermionGestureState.NULL; - AbstractGizmo? _gizmo; - Timer? _scrollTimer; - ThermionEntity? _highlightedEntity; - StreamSubscription? _pickResultSubscription; - - bool _gizmoAttached = false; - - PickingCameraGestureHandler({ - required this.viewer, - this.enableCamera = true, - this.enablePicking = true, - }) { - try { - _gizmo = viewer.gizmo; - } catch (err) { - _logger.warning( - "Failed to get gizmo. If you are running on WASM, this is expected"); - } - - _pickResultSubscription = viewer.pickResult.listen(_onPickResult); - - // Add keyboard listener - RawKeyboard.instance.addListener(_handleKeyEvent); - } - - @override - ThermionGestureState get currentState => _currentState; - - void _handleKeyEvent(RawKeyEvent event) { - if (event is RawKeyDownEvent && - event.logicalKey == LogicalKeyboardKey.escape) { - _resetToNullState(); - } - } - - void _resetToNullState() async { - _currentState = ThermionGestureState.NULL; - if (_highlightedEntity != null) { - await viewer.removeStencilHighlight(_highlightedEntity!); - _highlightedEntity = null; - } - } - - void _onPickResult(FilamentPickResult result) async { - var targetEntity = await viewer.getAncestor(result.entity) ?? result.entity; - - if (_highlightedEntity != targetEntity) { - if (_highlightedEntity != null) { - await viewer.removeStencilHighlight(_highlightedEntity!); - } - - _highlightedEntity = targetEntity; - if (_highlightedEntity != null) { - await viewer.setStencilHighlight(_highlightedEntity!); - _gizmo?.attach(_highlightedEntity!); - } - } - } - - @override - Future onPointerHover(Offset localPosition, Offset delta) async { - if (_gizmoAttached) { - _gizmo?.checkHover(localPosition.dx, localPosition.dy); - } - - if (_highlightedEntity != null) { - await viewer.queuePositionUpdateFromViewportCoords( - _highlightedEntity!, - localPosition.dx, - localPosition.dy, - ); - } - } - - @override - Future onPointerScroll(Offset localPosition, double scrollDelta) async { - if (!enableCamera) { - return; - } - if (_currentState == ThermionGestureState.NULL || - _currentState == ThermionGestureState.ZOOMING) { - await _zoom(localPosition, scrollDelta); - } - } - - @override - Future onPointerDown(Offset localPosition, int buttons) async { - if (_highlightedEntity != null) { - _resetToNullState(); - return; - } - - if (enablePicking && buttons != kMiddleMouseButton) { - viewer.pick(localPosition.dx.toInt(), localPosition.dy.toInt()); - } - - if (buttons == kMiddleMouseButton && enableCamera) { - await viewer.rotateStart(localPosition.dx, localPosition.dy); - _currentState = ThermionGestureState.ROTATING; - } else if (buttons == kPrimaryMouseButton && enableCamera) { - await viewer.panStart(localPosition.dx, localPosition.dy); - _currentState = ThermionGestureState.PANNING; - } - } - - @override - Future onPointerMove( - Offset localPosition, Offset delta, int buttons) async { - if (_highlightedEntity != null) { - await _handleEntityHighlightedMove(localPosition); - return; - } - - switch (_currentState) { - case ThermionGestureState.NULL: - break; - - case ThermionGestureState.ROTATING: - if (enableCamera) { - await viewer.rotateUpdate(localPosition.dx, localPosition.dy); - } - break; - case ThermionGestureState.PANNING: - if (enableCamera) { - await viewer.panUpdate(localPosition.dx, localPosition.dy); - } - break; - case ThermionGestureState.ZOOMING: - // ignore - break; - } - } - - @override - Future onPointerUp(int buttons) async { - switch (_currentState) { - case ThermionGestureState.ROTATING: - await viewer.rotateEnd(); - _currentState = ThermionGestureState.NULL; - break; - case ThermionGestureState.PANNING: - await viewer.panEnd(); - _currentState = ThermionGestureState.NULL; - break; - default: - break; - } - } - - Future _handleEntityHighlightedMove(Offset localPosition) async { - if (_highlightedEntity != null) { - await viewer.queuePositionUpdateFromViewportCoords( - _highlightedEntity!, - localPosition.dx, - localPosition.dy, - ); - } - } - - Future _zoom(Offset localPosition, double scrollDelta) async { - _scrollTimer?.cancel(); - _currentState = ThermionGestureState.ZOOMING; - await viewer.zoomBegin(); - await viewer.zoomUpdate( - localPosition.dx, localPosition.dy, scrollDelta > 0 ? 1 : -1); - - _scrollTimer = Timer(const Duration(milliseconds: 100), () async { - await viewer.zoomEnd(); - _currentState = ThermionGestureState.NULL; - }); - } - - @override - void dispose() { - _pickResultSubscription?.cancel(); - if (_highlightedEntity != null) { - viewer.removeStencilHighlight(_highlightedEntity!); - } - RawKeyboard.instance.removeListener(_handleKeyEvent); - } - - @override - Future get initialized => viewer.initialized; - - @override - GestureAction getActionForType(GestureType type) { - // TODO: implement getActionForType - throw UnimplementedError(); - } - - @override - Future onScaleEnd() { - // TODO: implement onScaleEnd - throw UnimplementedError(); - } - - @override - Future onScaleStart() { - // TODO: implement onScaleStart - throw UnimplementedError(); - } - - @override - Future onScaleUpdate() { - // TODO: implement onScaleUpdate - throw UnimplementedError(); - } - - @override - void setActionForType(GestureType type, GestureAction action) { - // TODO: implement setActionForType - } -} diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/camera/gestures/mobile_gesture_handler_selector_widget.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/camera/gestures/mobile_gesture_handler_selector_widget.dart deleted file mode 100644 index 3cff8d0e..00000000 --- a/thermion_flutter/thermion_flutter/lib/src/widgets/camera/gestures/mobile_gesture_handler_selector_widget.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:thermion_flutter/src/widgets/camera/gestures/thermion_gesture_handler.dart'; - -class MobileGestureHandlerSelectorWidget extends StatelessWidget { - final ThermionGestureHandler handler; - - const MobileGestureHandlerSelectorWidget({super.key, required this.handler}); - @override - Widget build(BuildContext context) { - throw Exception("TODO"); - // return GestureDetector( - // onTap: () { - - // var curIdx = - // GestureType.values.indexOf(handler.gestureType); - // var nextIdx = - // curIdx == GestureType.values.length - 1 ? 0 : curIdx + 1; - // handler.setGestureType(GestureType.values[nextIdx]); - // }); - // }, - // child: Container( - // padding: const EdgeInsets.all(50), - // child: Icon(_icons[widget.gestureHandler.gestureType], - // color: Colors.green), - // ), - // ); - } -} diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/camera/gestures/thermion_gesture_detector_desktop.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/camera/gestures/thermion_gesture_detector_desktop.dart deleted file mode 100644 index 888c741d..00000000 --- a/thermion_flutter/thermion_flutter/lib/src/widgets/camera/gestures/thermion_gesture_detector_desktop.dart +++ /dev/null @@ -1,56 +0,0 @@ -import 'dart:async'; -import 'package:logging/logging.dart'; -import 'package:thermion_dart/thermion_dart/entities/abstract_gizmo.dart'; -import 'package:thermion_dart/thermion_dart/thermion_viewer.dart'; - -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:thermion_flutter/src/widgets/camera/gestures/thermion_gesture_handler.dart'; -import 'package:vector_math/vector_math_64.dart' as v64; - -class ThermionGestureDetectorDesktop extends StatefulWidget { - final Widget? child; - final ThermionGestureHandler gestureHandler; - final bool showControlOverlay; - final bool enableCamera; - final bool enablePicking; - - const ThermionGestureDetectorDesktop({ - Key? key, - required this.gestureHandler, - this.child, - this.showControlOverlay = false, - this.enableCamera = true, - this.enablePicking = true, - }) : super(key: key); - - @override - State createState() => _ThermionGestureDetectorDesktopState(); -} - -class _ThermionGestureDetectorDesktopState - extends State { - - @override - Widget build(BuildContext context) { - return Listener( - onPointerHover: (event) => - widget.gestureHandler.onPointerHover(event.localPosition, event.delta), - onPointerSignal: (PointerSignalEvent pointerSignal) { - if (pointerSignal is PointerScrollEvent) { - widget.gestureHandler.onPointerScroll( - pointerSignal.localPosition, pointerSignal.scrollDelta.dy); - } - }, - onPointerPanZoomStart: (pzs) { - throw Exception("TODO - is this a pinch zoom on laptop trackpad?"); - }, - onPointerDown: (d) => - widget.gestureHandler.onPointerDown(d.localPosition, d.buttons), - onPointerMove: (d) => - widget.gestureHandler.onPointerMove(d.localPosition, d.delta, d.buttons), - onPointerUp: (d) => widget.gestureHandler.onPointerUp(d.buttons), - child: widget.child, - ); - } -} diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/camera/gestures/thermion_gesture_handler.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/camera/gestures/thermion_gesture_handler.dart deleted file mode 100644 index 206bbf81..00000000 --- a/thermion_flutter/thermion_flutter/lib/src/widgets/camera/gestures/thermion_gesture_handler.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/gestures.dart'; -import 'package:flutter/services.dart'; -import 'package:thermion_flutter/src/gestures/delegates.dart'; - -enum GestureType { - LMB_DOWN, - LMB_HOLD_AND_MOVE, - LMB_UP, - LMB_HOVER, - MMB_DOWN, - MMB_HOLD_AND_MOVE, - MMB_UP, - MMB_HOVER, - SCALE1, - SCALE2, - SCROLLWHEEL, - POINTER_MOVE, - KEYDOWN -} - -enum GestureAction { - PAN_CAMERA, - ROTATE_CAMERA, - ZOOM_CAMERA, - TRANSLATE_ENTITY, - ROTATE_ENTITY, - PICK_ENTITY, - NONE -} - -enum ThermionGestureState { - NULL, - ROTATING, - PANNING, - ZOOMING, // aka SCROLL -} - -abstract class ThermionGestureHandler { - Future onPointerHover(Offset localPosition, Offset delta); - Future onPointerScroll(Offset localPosition, double scrollDelta); - Future onPointerDown(Offset localPosition, int buttons); - Future onPointerMove(Offset localPosition, Offset delta, int buttons); - Future onPointerUp(int buttons); - Future onScaleStart(); - Future onScaleUpdate(); - Future onScaleEnd(); - Future get initialized; - void dispose(); - - void setActionForType(GestureType gestureType, GestureAction gestureAction); - GestureAction? getActionForType(GestureType gestureType); -} diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/camera/gestures/thermion_listener_widget.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/camera/gestures/thermion_listener_widget.dart deleted file mode 100644 index 906cdc3e..00000000 --- a/thermion_flutter/thermion_flutter/lib/src/widgets/camera/gestures/thermion_listener_widget.dart +++ /dev/null @@ -1,132 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:thermion_flutter/src/widgets/camera/gestures/thermion_gesture_handler.dart'; - -/// -/// A widget that captures swipe/pointer events. -/// This is a dumb listener; events are forwarded to a [ThermionGestureHandler]. -/// -class ThermionListenerWidget extends StatelessWidget { - /// - /// The content to display below the gesture detector/listener widget. - /// This will usually be a ThermionWidget (so you can navigate by directly interacting with the viewport), but this is not necessary. - /// It is equally possible to render the viewport/gesture controls elsewhere in the widget hierarchy. The only requirement is that they share the same [FilamentViewer]. - /// - final Widget? child; - - /// - /// The handler to use for interpreting gestures/pointer movements. - /// - final ThermionGestureHandler gestureHandler; - - const ThermionListenerWidget({ - Key? key, - required this.gestureHandler, - this.child, - }) : super(key: key); - - bool get isDesktop => - kIsWeb || Platform.isLinux || Platform.isWindows || Platform.isMacOS; - - Widget _desktop() { - return Listener( - onPointerHover: (event) => - gestureHandler.onPointerHover(event.localPosition, event.delta), - onPointerSignal: (PointerSignalEvent pointerSignal) { - if (pointerSignal is PointerScrollEvent) { - gestureHandler.onPointerScroll( - pointerSignal.localPosition, pointerSignal.scrollDelta.dy); - } - }, - onPointerPanZoomStart: (pzs) { - throw Exception("TODO - is this a pinch zoom on laptop trackpad?"); - }, - onPointerDown: (d) => - gestureHandler.onPointerDown(d.localPosition, d.buttons), - onPointerMove: (d) => - gestureHandler.onPointerMove(d.localPosition, d.delta, d.buttons), - onPointerUp: (d) => gestureHandler.onPointerUp(d.buttons), - child: child, - ); - } - - Widget _mobile() { - return _MobileListenerWidget( - gestureHandler: gestureHandler); - } - - @override - Widget build(BuildContext context) { - return FutureBuilder( - future: gestureHandler.initialized, - builder: (_, initialized) { - if (initialized.data != true) { - return child ?? Container(); - } - return Stack(children: [ - if (child != null) Positioned.fill(child: child!), - if (isDesktop) - Positioned.fill( - child: _desktop()), - if (!isDesktop) - Positioned.fill( - child: _mobile()) - ]); - }); - } -} - - -class _MobileListenerWidget extends StatefulWidget { - final ThermionGestureHandler gestureHandler; - - const _MobileListenerWidget( - {Key? key, required this.gestureHandler}) - : super(key: key); - - @override - State createState() => _MobileListenerWidgetState(); -} - -class _MobileListenerWidgetState - extends State<_MobileListenerWidget> { - GestureAction current = GestureAction.PAN_CAMERA; - - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - return GestureDetector( - behavior: HitTestBehavior.translucent, - onTapDown: (details) => - widget.gestureHandler.onPointerDown(details.localPosition, 0), - onDoubleTap: () { - if (current == GestureAction.PAN_CAMERA) { - widget.gestureHandler.setActionForType( - GestureType.SCALE1, GestureAction.ROTATE_CAMERA); - current = GestureAction.ROTATE_CAMERA; - } else { - widget.gestureHandler.setActionForType( - GestureType.SCALE1, GestureAction.PAN_CAMERA); - current = GestureAction.PAN_CAMERA; - } - }, - onScaleStart: (details) async { - await widget.gestureHandler.onScaleStart(); - }, - onScaleUpdate: (details) async { - await widget.gestureHandler.onScaleUpdate(); - }, - onScaleEnd: (details) async { - await widget.gestureHandler.onScaleUpdate(); - }, - ); - - } -} diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/camera/camera_options_widget.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/camera_options_widget.dart similarity index 99% rename from thermion_flutter/thermion_flutter/lib/src/widgets/camera/camera_options_widget.dart rename to thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/camera_options_widget.dart index b3d543f4..4a3296cf 100644 --- a/thermion_flutter/thermion_flutter/lib/src/widgets/camera/camera_options_widget.dart +++ b/thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/camera_options_widget.dart @@ -1,6 +1,6 @@ import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';import 'package:flutter/material.dart'; -import '../../utils/camera_orientation.dart'; +import '../../../utils/camera_orientation.dart'; import 'dart:math'; diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/camera/camera_orientation_widget.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/camera_orientation_widget.dart similarity index 100% rename from thermion_flutter/thermion_flutter/lib/src/widgets/camera/camera_orientation_widget.dart rename to thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/camera_orientation_widget.dart diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/gestures/mobile_gesture_handler_selector_widget.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/gestures/mobile_gesture_handler_selector_widget.dart new file mode 100644 index 00000000..de0cbe79 --- /dev/null +++ b/thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/gestures/mobile_gesture_handler_selector_widget.dart @@ -0,0 +1,28 @@ +// import 'package:flutter/widgets.dart'; +// import 'package:thermion_dart/thermion_dart/input/input_handler.dart'; + +// class MobileGestureHandlerSelectorWidget extends StatelessWidget { +// final InputHandler handler; + +// const MobileGestureHandlerSelectorWidget({super.key, required this.handler}); +// @override +// Widget build(BuildContext context) { +// throw Exception("TODO"); +// // return GestureDetector( +// // onTap: () { + +// // var curIdx = +// // InputType.values.indexOf(handler.gestureType); +// // var nextIdx = +// // curIdx == InputType.values.length - 1 ? 0 : curIdx + 1; +// // handler.setInputType(InputType.values[nextIdx]); +// // }); +// // }, +// // child: Container( +// // padding: const EdgeInsets.all(50), +// // child: Icon(_icons[widget.gestureHandler.gestureType], +// // color: Colors.green), +// // ), +// // ); +// } +// } diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/camera/gestures/thermion_gesture_detector.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/gestures/thermion_gesture_detector.dart similarity index 100% rename from thermion_flutter/thermion_flutter/lib/src/widgets/camera/gestures/thermion_gesture_detector.dart rename to thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/gestures/thermion_gesture_detector.dart diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/gestures/thermion_gesture_detector_desktop.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/gestures/thermion_gesture_detector_desktop.dart new file mode 100644 index 00000000..5ce2e627 --- /dev/null +++ b/thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/gestures/thermion_gesture_detector_desktop.dart @@ -0,0 +1,60 @@ +// import 'package:thermion_dart/thermion_dart.dart'; + +// import 'package:flutter/gestures.dart'; +// import 'package:flutter/material.dart'; + +// import 'package:vector_math/vector_math_64.dart'; +// import 'dart:ui' show Offset; + +// extension OffsetExtension on Offset { +// Vector2 toVector2() { +// return Vector2(dx, dy); +// } +// } + +// class ThermionGestureDetectorDesktop extends StatefulWidget { +// final Widget? child; +// final InputHandler gestureHandler; +// final bool showControlOverlay; +// final bool enableCamera; +// final bool enablePicking; + +// const ThermionGestureDetectorDesktop({ +// Key? key, +// required this.gestureHandler, +// this.child, +// this.showControlOverlay = false, +// this.enableCamera = true, +// this.enablePicking = true, +// }) : super(key: key); + +// @override +// State createState() => _ThermionGestureDetectorDesktopState(); +// } + +// class _ThermionGestureDetectorDesktopState +// extends State { + +// @override +// Widget build(BuildContext context) { +// return Listener( +// onPointerHover: (event) => +// widget.gestureHandler.onPointerHover(event.localPosition.toVector2(), event.delta.toVector2()), +// onPointerSignal: (PointerSignalEvent pointerSignal) { +// if (pointerSignal is PointerScrollEvent) { +// widget.gestureHandler.onPointerScroll( +// pointerSignal.localPosition.toVector2(), pointerSignal.scrollDelta.dy); +// } +// }, +// onPointerPanZoomStart: (pzs) { +// throw Exception("TODO - is this a pinch zoom on laptop trackpad?"); +// }, +// onPointerDown: (d) => +// widget.gestureHandler.onPointerDown(d.localPosition.toVector2(), d.buttons & kMiddleMouseButton != 0), +// onPointerMove: (PointerMoveEvent d) => +// widget.gestureHandler.onPointerMove(d.localPosition.toVector2(), d.delta.toVector2(), d.buttons & kMiddleMouseButton != 0), +// onPointerUp: (d) => widget.gestureHandler.onPointerUp(d.buttons), +// child: widget.child, +// ); +// } +// } diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/camera/gestures/thermion_gesture_detector_mobile.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/gestures/thermion_gesture_detector_mobile.dart similarity index 92% rename from thermion_flutter/thermion_flutter/lib/src/widgets/camera/gestures/thermion_gesture_detector_mobile.dart rename to thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/gestures/thermion_gesture_detector_mobile.dart index efc4bfc8..066c631d 100644 --- a/thermion_flutter/thermion_flutter/lib/src/widgets/camera/gestures/thermion_gesture_detector_mobile.dart +++ b/thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/gestures/thermion_gesture_detector_mobile.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:thermion_dart/thermion_dart/thermion_viewer.dart'; import 'package:flutter/material.dart'; -enum GestureType { rotateCamera, panCamera, panBackground } +enum InputType { rotateCamera, panCamera, panBackground } /// /// A widget that translates finger/mouse gestures to zoom/pan/rotate actions. @@ -62,12 +62,12 @@ class ThermionGestureDetectorMobile extends StatefulWidget { class _ThermionGestureDetectorMobileState extends State { - GestureType gestureType = GestureType.panCamera; + InputType gestureType = InputType.panCamera; final _icons = { - GestureType.panBackground: Icons.image, - GestureType.panCamera: Icons.pan_tool, - GestureType.rotateCamera: Icons.rotate_90_degrees_ccw + InputType.panBackground: Icons.image, + InputType.panCamera: Icons.pan_tool, + InputType.rotateCamera: Icons.rotate_90_degrees_ccw }; // on mobile, we can't differentiate between pointer down events like we do on desktop with primary/secondary/tertiary buttons @@ -97,18 +97,18 @@ class _ThermionGestureDetectorMobileState void _setFunction() { switch (gestureType) { - case GestureType.rotateCamera: + case InputType.rotateCamera: _functionStart = widget.viewer.rotateStart; _functionUpdate = widget.viewer.rotateUpdate; _functionEnd = widget.viewer.rotateEnd; break; - case GestureType.panCamera: + case InputType.panCamera: _functionStart = widget.viewer.panStart; _functionUpdate = widget.viewer.panUpdate; _functionEnd = widget.viewer.panEnd; break; // TODO - case GestureType.panBackground: + case InputType.panBackground: _functionStart = (x, y) async {}; _functionUpdate = (x, y) async {}; _functionEnd = () async {}; @@ -216,11 +216,11 @@ class _ThermionGestureDetectorMobileState child: GestureDetector( onTap: () { setState(() { - var curIdx = GestureType.values.indexOf(gestureType); - var nextIdx = curIdx == GestureType.values.length - 1 + var curIdx = InputType.values.indexOf(gestureType); + var nextIdx = curIdx == InputType.values.length - 1 ? 0 : curIdx + 1; - gestureType = GestureType.values[nextIdx]; + gestureType = InputType.values[nextIdx]; _setFunction(); }); }, diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/gestures/thermion_listener_widget.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/gestures/thermion_listener_widget.dart new file mode 100644 index 00000000..d0a50817 --- /dev/null +++ b/thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/gestures/thermion_listener_widget.dart @@ -0,0 +1,165 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:thermion_dart/thermion_dart.dart'; +import 'package:vector_math/vector_math_64.dart'; + +extension OffsetExtension on Offset { + Vector2 toVector2() { + return Vector2(dx, dy); + } +} + +/// +/// A widget that captures swipe/pointer events. +/// This is a dumb listener; events are forwarded to a [InputHandler]. +/// +class ThermionListenerWidget extends StatefulWidget { + /// + /// The content to display below the gesture detector/listener widget. + /// This will usually be a ThermionWidget (so you can navigate by directly interacting with the viewport), but this is not necessary. + /// It is equally possible to render the viewport/gesture controls elsewhere in the widget hierarchy. The only requirement is that they share the same [FilamentViewer]. + /// + final Widget? child; + + /// + /// The handler to use for interpreting gestures/pointer movements. + /// + final InputHandler gestureHandler; + + const ThermionListenerWidget({ + Key? key, + required this.gestureHandler, + this.child, + }) : super(key: key); + + @override + State createState() => _ThermionListenerWidgetState(); +} + +class _ThermionListenerWidgetState extends State { + bool get isDesktop => + kIsWeb || Platform.isLinux || Platform.isWindows || Platform.isMacOS; + + @override + void initState() { + super.initState(); + HardwareKeyboard.instance.addHandler(_handleKeyEvent); + } + + final _keyMap = { + PhysicalKeyboardKey.keyW: PhysicalKey.W, + PhysicalKeyboardKey.keyA: PhysicalKey.A, + PhysicalKeyboardKey.keyS: PhysicalKey.S, + PhysicalKeyboardKey.keyD: PhysicalKey.D, + }; + + bool _handleKeyEvent(KeyEvent event) { + PhysicalKey? key = _keyMap[event.physicalKey]; + + if (key == null) { + return false; + } + + if (event is KeyDownEvent || event is KeyRepeatEvent) { + widget.gestureHandler.keyDown(key!); + } else if (event is KeyUpEvent) { + widget.gestureHandler.keyUp(key!); + return true; + } + return false; + } + + @override + void dispose() { + super.dispose(); + HardwareKeyboard.instance.removeHandler(_handleKeyEvent); + } + + Widget _desktop() { + return Listener( + onPointerHover: (event) => widget.gestureHandler + .onPointerHover(event.localPosition.toVector2(), event.delta.toVector2()), + onPointerSignal: (PointerSignalEvent pointerSignal) { + if (pointerSignal is PointerScrollEvent) { + widget.gestureHandler.onPointerScroll( + pointerSignal.localPosition.toVector2(), + pointerSignal.scrollDelta.dy); + } + }, + onPointerPanZoomStart: (pzs) { + throw Exception("TODO - is this a pinch zoom on laptop trackpad?"); + }, + onPointerDown: (d) => widget.gestureHandler + .onPointerDown(d.localPosition.toVector2(), d.buttons & kMiddleMouseButton != 0), + onPointerMove: (d) => widget.gestureHandler + .onPointerMove(d.localPosition.toVector2(), d.delta.toVector2(), d.buttons & kMiddleMouseButton != 0), + onPointerUp: (d) => widget.gestureHandler.onPointerUp(d.buttons & kMiddleMouseButton != 0), + child: widget.child, + ); + } + + Widget _mobile() { + return _MobileListenerWidget(gestureHandler: widget.gestureHandler); + } + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: widget.gestureHandler.initialized, + builder: (_, initialized) { + if (initialized.data != true) { + return widget.child ?? Container(); + } + return Stack(children: [ + if (widget.child != null) Positioned.fill(child: widget.child!), + if (isDesktop) Positioned.fill(child: _desktop()), + if (!isDesktop) Positioned.fill(child: _mobile()) + ]); + }); + } +} + +class _MobileListenerWidget extends StatefulWidget { + final InputHandler gestureHandler; + + const _MobileListenerWidget({Key? key, required this.gestureHandler}) + : super(key: key); + + @override + State createState() => _MobileListenerWidgetState(); +} + +class _MobileListenerWidgetState extends State<_MobileListenerWidget> { + bool isPan = true; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + behavior: HitTestBehavior.translucent, + onTapDown: (details) => widget.gestureHandler + .onPointerDown(details.localPosition.toVector2(), false), + onDoubleTap: () { + widget.gestureHandler.setActionForType(InputType.SCALE1, + isPan ? InputAction.TRANSLATE : InputAction.ROTATE); + }, + onScaleStart: (details) async { + await widget.gestureHandler.onScaleStart(); + }, + onScaleUpdate: (details) async { + await widget.gestureHandler.onScaleUpdate(); + }, + onScaleEnd: (details) async { + await widget.gestureHandler.onScaleUpdate(); + }, + ); + } +} diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/debug/child_renderable_widget.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/debug/child_renderable_widget.dart similarity index 100% rename from thermion_flutter/thermion_flutter/lib/src/widgets/debug/child_renderable_widget.dart rename to thermion_flutter/thermion_flutter/lib/src/widgets/src/debug/child_renderable_widget.dart diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/debug/skeleton_menu_item_widget.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/debug/skeleton_menu_item_widget.dart similarity index 100% rename from thermion_flutter/thermion_flutter/lib/src/widgets/debug/skeleton_menu_item_widget.dart rename to thermion_flutter/thermion_flutter/lib/src/widgets/src/debug/skeleton_menu_item_widget.dart diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/lights/ibl_rotation_slider.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/lights/ibl_rotation_slider.dart similarity index 100% rename from thermion_flutter/thermion_flutter/lib/src/widgets/lights/ibl_rotation_slider.dart rename to thermion_flutter/thermion_flutter/lib/src/widgets/src/lights/ibl_rotation_slider.dart diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/lights/light_slider.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/lights/light_slider.dart similarity index 100% rename from thermion_flutter/thermion_flutter/lib/src/widgets/lights/light_slider.dart rename to thermion_flutter/thermion_flutter/lib/src/widgets/src/lights/light_slider.dart diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/resize_observer.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/resize_observer.dart similarity index 100% rename from thermion_flutter/thermion_flutter/lib/src/widgets/resize_observer.dart rename to thermion_flutter/thermion_flutter/lib/src/widgets/src/resize_observer.dart diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/thermion_widget.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget.dart similarity index 96% rename from thermion_flutter/thermion_flutter/lib/src/widgets/thermion_widget.dart rename to thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget.dart index 6d5aac09..31cf49cc 100644 --- a/thermion_flutter/thermion_flutter/lib/src/widgets/thermion_widget.dart +++ b/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget.dart @@ -2,8 +2,8 @@ import 'dart:io'; import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:thermion_flutter/src/widgets/thermion_widget_web.dart'; -import 'package:thermion_flutter/src/widgets/transparent_filament_widget.dart'; +import 'package:thermion_flutter/src/widgets/src/thermion_widget_web.dart'; +import 'package:thermion_flutter/src/widgets/src/transparent_filament_widget.dart'; import 'dart:async'; import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart'; @@ -12,9 +12,11 @@ import 'package:thermion_flutter_web/thermion_flutter_web_options.dart'; import 'resize_observer.dart'; class ThermionWidget extends StatefulWidget { + final ThermionViewer viewer; final ThermionFlutterOptions? options; + /// /// The content to render before the texture widget is available. /// The default is a solid red Container, intentionally chosen to make it clear that there will be at least one frame where the Texture widget is not being rendered. @@ -63,6 +65,7 @@ class _ThermionWidgetState extends State { void _requestFrame() { WidgetsBinding.instance.scheduleFrameCallback((d) { + widget.viewer.requestFrame(); _requestFrame(); }); diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/thermion_widget_web.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget_web.dart similarity index 100% rename from thermion_flutter/thermion_flutter/lib/src/widgets/thermion_widget_web.dart rename to thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget_web.dart diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/thermion_widget_web_impl.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget_web_impl.dart similarity index 100% rename from thermion_flutter/thermion_flutter/lib/src/widgets/thermion_widget_web_impl.dart rename to thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget_web_impl.dart diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/thermion_widget_web_stub.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget_web_stub.dart similarity index 100% rename from thermion_flutter/thermion_flutter/lib/src/widgets/thermion_widget_web_stub.dart rename to thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget_web_stub.dart diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/transparent_filament_widget.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/transparent_filament_widget.dart similarity index 100% rename from thermion_flutter/thermion_flutter/lib/src/widgets/transparent_filament_widget.dart rename to thermion_flutter/thermion_flutter/lib/src/widgets/src/transparent_filament_widget.dart diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/widgets.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/widgets.dart new file mode 100644 index 00000000..de0efe1d --- /dev/null +++ b/thermion_flutter/thermion_flutter/lib/src/widgets/widgets.dart @@ -0,0 +1,4 @@ +library; + +export 'src/thermion_widget.dart'; +export 'src/camera/gestures/thermion_listener_widget.dart'; diff --git a/thermion_flutter/thermion_flutter/lib/thermion_flutter.dart b/thermion_flutter/thermion_flutter/lib/thermion_flutter.dart index d55ed8e1..8d69510a 100644 --- a/thermion_flutter/thermion_flutter/lib/thermion_flutter.dart +++ b/thermion_flutter/thermion_flutter/lib/thermion_flutter.dart @@ -1,11 +1,6 @@ library thermion_flutter; export 'src/thermion_flutter_plugin.dart'; -export 'src/widgets/thermion_widget.dart'; -export 'src/widgets/camera/gestures/thermion_gesture_detector.dart'; -export 'src/widgets/camera/gestures/thermion_gesture_handler.dart'; -export 'src/gestures/delegate_gesture_handler.dart'; - -export 'src/widgets/camera/camera_orientation_widget.dart'; +export 'src/widgets/widgets.dart'; export 'package:thermion_flutter_platform_interface/thermion_flutter_platform_interface.dart'; export 'package:thermion_dart/thermion_dart.dart';