gesture handler & delegate improvements
This commit is contained in:
@@ -76,7 +76,7 @@ class PickingCameraGestureHandler implements ThermionGestureHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onPointerHover(Offset localPosition) async {
|
Future<void> onPointerHover(Offset localPosition, Offset delta) async {
|
||||||
if (_gizmoAttached) {
|
if (_gizmoAttached) {
|
||||||
_gizmo?.checkHover(localPosition.dx, localPosition.dy);
|
_gizmo?.checkHover(localPosition.dx, localPosition.dy);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,17 +5,18 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/delegates.dart';
|
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/delegates.dart';
|
||||||
|
|
||||||
enum GestureType {
|
enum GestureType {
|
||||||
POINTER1_DOWN,
|
LMB_DOWN,
|
||||||
POINTER1_MOVE,
|
LMB_HOLD_AND_MOVE,
|
||||||
POINTER1_UP,
|
LMB_UP,
|
||||||
POINTER1_HOVER,
|
LMB_HOVER,
|
||||||
POINTER2_DOWN,
|
MMB_DOWN,
|
||||||
POINTER2_MOVE,
|
MMB_HOLD_AND_MOVE,
|
||||||
POINTER2_UP,
|
MMB_UP,
|
||||||
POINTER2_HOVER,
|
MMB_HOVER,
|
||||||
SCALE1,
|
SCALE1,
|
||||||
SCALE2,
|
SCALE2,
|
||||||
POINTER_ZOOM,
|
SCROLLWHEEL,
|
||||||
|
POINTER_MOVE
|
||||||
}
|
}
|
||||||
|
|
||||||
enum GestureAction {
|
enum GestureAction {
|
||||||
@@ -23,7 +24,8 @@ enum GestureAction {
|
|||||||
ROTATE_CAMERA,
|
ROTATE_CAMERA,
|
||||||
ZOOM_CAMERA,
|
ZOOM_CAMERA,
|
||||||
TRANSLATE_ENTITY,
|
TRANSLATE_ENTITY,
|
||||||
ROTATE_ENTITY
|
ROTATE_ENTITY,
|
||||||
|
NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ThermionGestureState {
|
enum ThermionGestureState {
|
||||||
@@ -34,7 +36,7 @@ enum ThermionGestureState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class ThermionGestureHandler {
|
abstract class ThermionGestureHandler {
|
||||||
Future<void> onPointerHover(Offset localPosition);
|
Future<void> onPointerHover(Offset localPosition, Offset delta);
|
||||||
Future<void> onPointerScroll(Offset localPosition, double scrollDelta);
|
Future<void> onPointerScroll(Offset localPosition, double scrollDelta);
|
||||||
Future<void> onPointerDown(Offset localPosition, int buttons);
|
Future<void> onPointerDown(Offset localPosition, int buttons);
|
||||||
Future<void> onPointerMove(Offset localPosition, Offset delta, int buttons);
|
Future<void> onPointerMove(Offset localPosition, Offset delta, int buttons);
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class ThermionListenerWidget extends StatelessWidget {
|
|||||||
Widget _desktop() {
|
Widget _desktop() {
|
||||||
return Listener(
|
return Listener(
|
||||||
onPointerHover: (event) =>
|
onPointerHover: (event) =>
|
||||||
gestureHandler.onPointerHover(event.localPosition),
|
gestureHandler.onPointerHover(event.localPosition, event.delta),
|
||||||
onPointerSignal: (PointerSignalEvent pointerSignal) {
|
onPointerSignal: (PointerSignalEvent pointerSignal) {
|
||||||
if (pointerSignal is PointerScrollEvent) {
|
if (pointerSignal is PointerScrollEvent) {
|
||||||
gestureHandler.onPointerScroll(
|
gestureHandler.onPointerScroll(
|
||||||
|
|||||||
@@ -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_flutter/thermion/widgets/camera/gestures/v2/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<PhysicalKeyboardKey, bool> _pressedKeys = {};
|
||||||
|
Timer? _moveTimer;
|
||||||
|
|
||||||
|
DefaultKeyboardCameraFlightDelegate(this.viewer) {
|
||||||
|
_startMoveLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> 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<void> onKeypress(PhysicalKeyboardKey key) async {
|
||||||
|
_pressedKeys[key] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// New method to handle key release
|
||||||
|
Future<void> onKeyRelease(PhysicalKeyboardKey key) async {
|
||||||
|
_pressedKeys.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startMoveLoop() {
|
||||||
|
_moveTimer = Timer.periodic(
|
||||||
|
Duration(milliseconds: 16), (_) => _processKeyboardInput());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _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<void> _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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,34 +1,35 @@
|
|||||||
import 'dart:ui';
|
// import 'dart:ui';
|
||||||
|
|
||||||
import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
|
// import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
|
||||||
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/delegates.dart';
|
// import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/delegates.dart';
|
||||||
import 'package:vector_math/vector_math_64.dart';
|
// import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
class DefaultPanCameraDelegate implements PanCameraDelegate {
|
// class DefaultPanCameraDelegate implements PanCameraDelegate {
|
||||||
final ThermionViewer viewer;
|
// final ThermionViewer viewer;
|
||||||
|
|
||||||
static const double _panSensitivity = 0.005;
|
// static const double _panSensitivity = 0.005;
|
||||||
|
|
||||||
DefaultPanCameraDelegate(this.viewer);
|
// DefaultPanCameraDelegate(this.viewer);
|
||||||
|
// static const double _panSensitivity = 0.005;
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
Future<void> panCamera(Offset delta, Vector2? velocity) async {
|
// Future<void> panCamera(Offset delta, Vector2? velocity) async {
|
||||||
double deltaX = delta.dx;
|
// double deltaX = delta.dx;
|
||||||
double deltaY = delta.dy;
|
// double deltaY = delta.dy;
|
||||||
deltaX *= _panSensitivity * viewer.pixelRatio;
|
// deltaX *= _panSensitivity * viewer.pixelRatio;
|
||||||
deltaY *= _panSensitivity * viewer.pixelRatio;
|
// deltaY *= _panSensitivity * viewer.pixelRatio;
|
||||||
|
|
||||||
Matrix4 currentModelMatrix = await viewer.getCameraModelMatrix();
|
// Matrix4 currentModelMatrix = await viewer.getCameraModelMatrix();
|
||||||
Vector3 currentPosition = currentModelMatrix.getTranslation();
|
// Vector3 currentPosition = currentModelMatrix.getTranslation();
|
||||||
Quaternion currentRotation = Quaternion.fromRotation(currentModelMatrix.getRotation());
|
// Quaternion currentRotation = Quaternion.fromRotation(currentModelMatrix.getRotation());
|
||||||
|
|
||||||
Vector3 right = Vector3(1, 0, 0)..applyQuaternion(currentRotation);
|
// Vector3 right = Vector3(1, 0, 0)..applyQuaternion(currentRotation);
|
||||||
Vector3 up = Vector3(0, 1, 0)..applyQuaternion(currentRotation);
|
// Vector3 up = Vector3(0, 1, 0)..applyQuaternion(currentRotation);
|
||||||
|
|
||||||
Vector3 panOffset = right * -deltaX + up * deltaY;
|
// Vector3 panOffset = right * -deltaX + up * deltaY;
|
||||||
Vector3 newPosition = currentPosition + panOffset;
|
// Vector3 newPosition = currentPosition + panOffset;
|
||||||
|
|
||||||
Matrix4 newModelMatrix = Matrix4.compose(newPosition, currentRotation, Vector3(1, 1, 1));
|
// Matrix4 newModelMatrix = Matrix4.compose(newPosition, currentRotation, Vector3(1, 1, 1));
|
||||||
await viewer.setCameraModelMatrix4(newModelMatrix);
|
// await viewer.setCameraModelMatrix4(newModelMatrix);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
@@ -5,23 +5,16 @@ import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
|
|||||||
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/delegates.dart';
|
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/delegates.dart';
|
||||||
import 'package:vector_math/vector_math_64.dart';
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
class DefaultZoomCameraDelegate implements ZoomCameraDelegate {
|
class DefaultZoomCameraDelegate {
|
||||||
final ThermionViewer viewer;
|
final ThermionViewer viewer;
|
||||||
final double zoomSensitivity ;
|
final double zoomSensitivity;
|
||||||
|
|
||||||
final double? Function(Vector3 cameraPosition)? getDistanceToTarget;
|
final double? Function(Vector3 cameraPosition)? getDistanceToTarget;
|
||||||
|
|
||||||
DefaultZoomCameraDelegate(this.viewer, {this.zoomSensitivity = 0.005, this.getDistanceToTarget});
|
DefaultZoomCameraDelegate(this.viewer,
|
||||||
|
{this.zoomSensitivity = 0.005, this.getDistanceToTarget});
|
||||||
@override
|
|
||||||
Future<void> zoomCamera(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();
|
|
||||||
|
|
||||||
|
double calculateZoomDistance(double scrollDelta, Vector2? velocity, Vector3 cameraPosition) {
|
||||||
double? distanceToTarget = getDistanceToTarget?.call(cameraPosition);
|
double? distanceToTarget = getDistanceToTarget?.call(cameraPosition);
|
||||||
double zoomDistance = scrollDelta * zoomSensitivity;
|
double zoomDistance = scrollDelta * zoomSensitivity;
|
||||||
if (distanceToTarget != null) {
|
if (distanceToTarget != null) {
|
||||||
@@ -30,7 +23,20 @@ class DefaultZoomCameraDelegate implements ZoomCameraDelegate {
|
|||||||
zoomDistance = scrollDelta * zoomSensitivity;
|
zoomDistance = scrollDelta * zoomSensitivity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
zoomDistance = max(zoomDistance, scrollDelta * zoomSensitivity);
|
return max(zoomDistance, scrollDelta * zoomSensitivity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> 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 =
|
||||||
|
calculateZoomDistance(scrollDelta, velocity, cameraPosition);
|
||||||
|
|
||||||
Vector3 newPosition = cameraPosition + (forwardVector * zoomDistance);
|
Vector3 newPosition = cameraPosition + (forwardVector * zoomDistance);
|
||||||
await viewer.setCameraPosition(newPosition.x, newPosition.y, newPosition.z);
|
await viewer.setCameraPosition(newPosition.x, newPosition.y, newPosition.z);
|
||||||
|
|||||||
@@ -1,161 +1,177 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/default_pan_camera_delegate.dart';
|
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/default_keyboard_camera_flight_delegate.dart';
|
||||||
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/default_velocity_delegate.dart';
|
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/default_velocity_delegate.dart';
|
||||||
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/default_zoom_camera_delegate.dart';
|
|
||||||
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/delegates.dart';
|
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/delegates.dart';
|
||||||
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/fixed_orbit_camera_rotation_delegate.dart';
|
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/fixed_orbit_camera_rotation_delegate.dart';
|
||||||
|
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/free_flight_camera_delegate.dart';
|
||||||
import 'package:thermion_flutter/thermion_flutter.dart';
|
import 'package:thermion_flutter/thermion_flutter.dart';
|
||||||
|
|
||||||
class DelegateGestureHandler implements ThermionGestureHandler {
|
class DelegateGestureHandler implements ThermionGestureHandler {
|
||||||
final ThermionViewer viewer;
|
final ThermionViewer viewer;
|
||||||
final Logger _logger = Logger("CustomGestureHandler");
|
final Logger _logger = Logger("CustomGestureHandler");
|
||||||
|
|
||||||
ThermionGestureState _currentState = ThermionGestureState.NULL;
|
CameraDelegate? cameraDelegate;
|
||||||
|
|
||||||
// Class-based delegates
|
|
||||||
RotateCameraDelegate? rotateCameraDelegate;
|
|
||||||
PanCameraDelegate? panCameraDelegate;
|
|
||||||
ZoomCameraDelegate? zoomCameraDelegate;
|
|
||||||
VelocityDelegate? velocityDelegate;
|
VelocityDelegate? velocityDelegate;
|
||||||
|
|
||||||
// Timer for continuous movement
|
Ticker? _ticker;
|
||||||
Timer? _velocityTimer;
|
static const _updateInterval = Duration(milliseconds: 16);
|
||||||
static const _velocityUpdateInterval = Duration(milliseconds: 16); // ~60 FPS
|
|
||||||
|
Map<GestureType, Offset> _accumulatedDeltas = {};
|
||||||
|
double _accumulatedScrollDelta = 0.0;
|
||||||
|
int _activePointers = 0;
|
||||||
|
bool _isMiddleMouseButtonPressed = false;
|
||||||
|
|
||||||
|
VoidCallback? _keyboardListenerDisposer;
|
||||||
|
|
||||||
|
final Map<GestureType, GestureAction> _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({
|
DelegateGestureHandler({
|
||||||
required this.viewer,
|
required this.viewer,
|
||||||
required this.rotateCameraDelegate,
|
required this.cameraDelegate,
|
||||||
required this.panCameraDelegate,
|
|
||||||
required this.zoomCameraDelegate,
|
|
||||||
required this.velocityDelegate,
|
required this.velocityDelegate,
|
||||||
});
|
Map<GestureType, GestureAction>? actions,
|
||||||
|
}) {
|
||||||
factory DelegateGestureHandler.withDefaults(ThermionViewer viewer) =>
|
_initializeKeyboardListener();
|
||||||
DelegateGestureHandler(
|
_initializeTicker();
|
||||||
viewer: viewer,
|
if (actions != null) {
|
||||||
rotateCameraDelegate: FixedOrbitRotateCameraDelegate(viewer),
|
_actions.addAll(actions);
|
||||||
panCameraDelegate: DefaultPanCameraDelegate(viewer),
|
}
|
||||||
zoomCameraDelegate: DefaultZoomCameraDelegate(viewer),
|
_initializeAccumulatedDeltas();
|
||||||
velocityDelegate: DefaultVelocityDelegate());
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onPointerDown(Offset localPosition, int buttons) async {
|
|
||||||
velocityDelegate?.stopDeceleration();
|
|
||||||
_stopVelocityTimer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GestureType? _lastGestureType;
|
factory DelegateGestureHandler.fixedOrbit(ThermionViewer viewer) =>
|
||||||
|
DelegateGestureHandler(
|
||||||
|
viewer: viewer,
|
||||||
|
cameraDelegate: FixedOrbitRotateCameraDelegate(viewer),
|
||||||
|
velocityDelegate: DefaultVelocityDelegate(),
|
||||||
|
);
|
||||||
|
|
||||||
|
factory DelegateGestureHandler.flight(ThermionViewer viewer) =>
|
||||||
|
DelegateGestureHandler(
|
||||||
|
viewer: viewer,
|
||||||
|
cameraDelegate: FreeFlightCameraDelegate(viewer),
|
||||||
|
velocityDelegate: DefaultVelocityDelegate(),
|
||||||
|
actions: {GestureType.POINTER_MOVE: GestureAction.ROTATE_CAMERA},
|
||||||
|
);
|
||||||
|
|
||||||
|
void _initializeAccumulatedDeltas() {
|
||||||
|
for (var gestureType in GestureType.values) {
|
||||||
|
_accumulatedDeltas[gestureType] = Offset.zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _initializeTicker() {
|
||||||
|
_ticker = Ticker(_onTick);
|
||||||
|
_ticker!.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onTick(Duration elapsed) async {
|
||||||
|
await _applyAccumulatedUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _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<void> onPointerDown(Offset localPosition, int buttons) async {
|
||||||
|
velocityDelegate?.stopDeceleration();
|
||||||
|
_activePointers++;
|
||||||
|
if (buttons & kMiddleMouseButton != 0) {
|
||||||
|
_isMiddleMouseButtonPressed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onPointerMove(
|
Future<void> onPointerMove(Offset localPosition, Offset delta, int buttons) async {
|
||||||
Offset localPosition, Offset delta, int buttons) async {
|
GestureType gestureType = _getGestureTypeFromButtons(buttons);
|
||||||
velocityDelegate?.updateVelocity(delta);
|
if (gestureType == GestureType.MMB_HOLD_AND_MOVE ||
|
||||||
|
(_actions[GestureType.POINTER_MOVE] == GestureAction.ROTATE_CAMERA && gestureType == GestureType.POINTER_MOVE)) {
|
||||||
GestureType gestureType;
|
_accumulatedDeltas[GestureType.MMB_HOLD_AND_MOVE] = (_accumulatedDeltas[GestureType.MMB_HOLD_AND_MOVE] ?? Offset.zero) + delta;
|
||||||
if (buttons == kPrimaryMouseButton) {
|
|
||||||
gestureType = GestureType.POINTER1_MOVE;
|
|
||||||
} else if (buttons == kMiddleMouseButton) {
|
|
||||||
gestureType = GestureType.POINTER2_MOVE;
|
|
||||||
} else {
|
} else {
|
||||||
throw Exception("Unsupported button: $buttons");
|
_accumulatedDeltas[gestureType] = (_accumulatedDeltas[gestureType] ?? Offset.zero) + delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
var action = _actions[gestureType];
|
|
||||||
|
|
||||||
switch (action) {
|
|
||||||
case GestureAction.PAN_CAMERA:
|
|
||||||
_currentState = ThermionGestureState.PANNING;
|
|
||||||
await panCameraDelegate?.panCamera(delta, velocityDelegate?.velocity);
|
|
||||||
case GestureAction.ROTATE_CAMERA:
|
|
||||||
_currentState = ThermionGestureState.ROTATING;
|
|
||||||
await rotateCameraDelegate?.rotateCamera(
|
|
||||||
delta, velocityDelegate?.velocity);
|
|
||||||
case null:
|
|
||||||
// ignore;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw Exception("Unsupported gesture type : $gestureType ");
|
|
||||||
}
|
|
||||||
|
|
||||||
_lastGestureType = gestureType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onPointerUp(int buttons) async {
|
Future<void> onPointerUp(int buttons) async {
|
||||||
_currentState = ThermionGestureState.NULL;
|
_activePointers--;
|
||||||
velocityDelegate?.startDeceleration();
|
if (_activePointers == 0) {
|
||||||
_startVelocityTimer();
|
velocityDelegate?.startDeceleration();
|
||||||
}
|
|
||||||
|
|
||||||
void _startVelocityTimer() {
|
|
||||||
_stopVelocityTimer(); // Ensure any existing timer is stopped
|
|
||||||
_velocityTimer = Timer.periodic(_velocityUpdateInterval, (timer) {
|
|
||||||
_applyVelocity();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _stopVelocityTimer() {
|
|
||||||
_velocityTimer?.cancel();
|
|
||||||
_velocityTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _applyVelocity() async {
|
|
||||||
final velocity = velocityDelegate?.velocity;
|
|
||||||
if (velocity == null || velocity.length < 0.1) {
|
|
||||||
_stopVelocityTimer();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
if (buttons & kMiddleMouseButton != 0) {
|
||||||
final lastAction = _actions[_lastGestureType];
|
_isMiddleMouseButtonPressed = false;
|
||||||
switch (lastAction) {
|
|
||||||
case GestureAction.PAN_CAMERA:
|
|
||||||
await panCameraDelegate?.panCamera(
|
|
||||||
Offset(velocity.x, velocity.y), velocity);
|
|
||||||
case GestureAction.ROTATE_CAMERA:
|
|
||||||
await rotateCameraDelegate?.rotateCamera(
|
|
||||||
Offset(velocity.x, velocity.y), velocity);
|
|
||||||
default:
|
|
||||||
// Do nothing for other actions
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
velocityDelegate?.updateVelocity(Offset(velocity.x, velocity.y)); // Gradually reduce velocity
|
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
|
@override
|
||||||
Future<void> onPointerHover(Offset localPosition) async {
|
Future<void> onPointerHover(Offset localPosition, Offset delta) async {
|
||||||
// TODO, currently noop
|
if (_actions[GestureType.POINTER_MOVE] == GestureAction.ROTATE_CAMERA) {
|
||||||
|
_accumulatedDeltas[GestureType.POINTER_MOVE] = (_accumulatedDeltas[GestureType.POINTER_MOVE] ?? Offset.zero) + delta;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onPointerScroll(Offset localPosition, double scrollDelta) async {
|
Future<void> onPointerScroll(Offset localPosition, double scrollDelta) async {
|
||||||
if (_currentState != ThermionGestureState.NULL) {
|
if (_actions[GestureType.SCROLLWHEEL] != GestureAction.ZOOM_CAMERA) {
|
||||||
return;
|
throw Exception("Unsupported action: ${_actions[GestureType.SCROLLWHEEL]}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_actions[GestureType.POINTER_ZOOM] != GestureAction.ZOOM_CAMERA) {
|
|
||||||
throw Exception(
|
|
||||||
"Unsupported action : ${_actions[GestureType.POINTER_ZOOM]}");
|
|
||||||
}
|
|
||||||
|
|
||||||
_currentState = ThermionGestureState.ZOOMING;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await zoomCameraDelegate?.zoomCamera(
|
_accumulatedScrollDelta += scrollDelta;
|
||||||
scrollDelta, velocityDelegate?.velocity);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_logger.warning("Error during camera zoom: $e");
|
_logger.warning("Error during scroll accumulation: $e");
|
||||||
} finally {
|
|
||||||
_currentState = ThermionGestureState.NULL;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_stopVelocityTimer();
|
|
||||||
velocityDelegate?.dispose();
|
velocityDelegate?.dispose();
|
||||||
|
_keyboardListenerDisposer?.call();
|
||||||
|
_ticker?.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -170,12 +186,6 @@ class DelegateGestureHandler implements ThermionGestureHandler {
|
|||||||
@override
|
@override
|
||||||
Future<void> onScaleUpdate() async {}
|
Future<void> onScaleUpdate() async {}
|
||||||
|
|
||||||
final _actions = {
|
|
||||||
GestureType.POINTER1_MOVE: GestureAction.PAN_CAMERA,
|
|
||||||
GestureType.POINTER2_MOVE: GestureAction.ROTATE_CAMERA,
|
|
||||||
GestureType.POINTER_ZOOM: GestureAction.ZOOM_CAMERA
|
|
||||||
};
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void setActionForType(GestureType gestureType, GestureAction gestureAction) {
|
void setActionForType(GestureType gestureType, GestureAction gestureAction) {
|
||||||
_actions[gestureType] = gestureAction;
|
_actions[gestureType] = gestureAction;
|
||||||
@@ -184,4 +194,22 @@ class DelegateGestureHandler implements ThermionGestureHandler {
|
|||||||
GestureAction? getActionForType(GestureType gestureType) {
|
GestureAction? getActionForType(GestureType gestureType) {
|
||||||
return _actions[gestureType];
|
return _actions[gestureType];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
void _initializeKeyboardListener() {
|
||||||
|
HardwareKeyboard.instance.addHandler(_handleKeyEvent);
|
||||||
|
_keyboardListenerDisposer = () {
|
||||||
|
HardwareKeyboard.instance.removeHandler(_handleKeyEvent);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _handleKeyEvent(KeyEvent event) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,13 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:vector_math/vector_math_64.dart';
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
abstract class RotateCameraDelegate {
|
abstract class CameraDelegate {
|
||||||
Future<void> rotateCamera(Offset delta, Vector2? velocity);
|
Future<void> rotate(Offset delta, Vector2? velocity);
|
||||||
}
|
Future<void> pan(Offset delta, Vector2? velocity);
|
||||||
|
Future<void> zoom(double scrollDelta, Vector2? velocity);
|
||||||
abstract class PanCameraDelegate {
|
Future<void> onKeypress(PhysicalKeyboardKey key);
|
||||||
Future<void> panCamera(Offset delta, Vector2? velocity);
|
Future<void> onKeyRelease(PhysicalKeyboardKey key);
|
||||||
}
|
|
||||||
|
|
||||||
abstract class ZoomCameraDelegate {
|
|
||||||
Future<void> zoomCamera(double scrollDelta, Vector2? velocity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class VelocityDelegate {
|
abstract class VelocityDelegate {
|
||||||
|
|||||||
@@ -1,45 +1,112 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/src/services/keyboard_key.g.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
|
import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
|
||||||
|
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/default_zoom_camera_delegate.dart';
|
||||||
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/delegates.dart';
|
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/delegates.dart';
|
||||||
import 'package:vector_math/vector_math_64.dart';
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
class FixedOrbitRotateCameraDelegate implements RotateCameraDelegate {
|
class FixedOrbitRotateCameraDelegate implements CameraDelegate {
|
||||||
final ThermionViewer viewer;
|
final ThermionViewer viewer;
|
||||||
static final _up = Vector3(0, 1, 0);
|
static final _up = Vector3(0, 1, 0);
|
||||||
static final _forward = Vector3(0, 0, -1);
|
static final _forward = Vector3(0, 0, -1);
|
||||||
|
static final Vector3 _right = Vector3(1, 0, 0);
|
||||||
|
|
||||||
static const double _rotationSensitivity = 0.01;
|
static const double _rotationSensitivity = 0.01;
|
||||||
|
|
||||||
FixedOrbitRotateCameraDelegate(this.viewer);
|
late DefaultZoomCameraDelegate _zoomCameraDelegate;
|
||||||
|
|
||||||
|
Offset _accumulatedRotationDelta = Offset.zero;
|
||||||
|
double _accumulatedZoomDelta = 0.0;
|
||||||
|
|
||||||
|
Timer? _updateTimer;
|
||||||
|
|
||||||
|
FixedOrbitRotateCameraDelegate(this.viewer) {
|
||||||
|
_zoomCameraDelegate = DefaultZoomCameraDelegate(this.viewer);
|
||||||
|
_startUpdateTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startUpdateTimer() {
|
||||||
|
_updateTimer = Timer.periodic(const Duration(milliseconds: 16), (_) {
|
||||||
|
_applyAccumulatedUpdates();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
_updateTimer?.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> rotateCamera(Offset delta, Vector2? velocity) async {
|
Future<void> rotate(Offset delta, Vector2? velocity) async {
|
||||||
double deltaX = delta.dx;
|
_accumulatedRotationDelta += delta;
|
||||||
double deltaY = delta.dy;
|
|
||||||
deltaX *= _rotationSensitivity * viewer.pixelRatio;
|
|
||||||
deltaY *= _rotationSensitivity * viewer.pixelRatio;
|
|
||||||
|
|
||||||
Matrix4 currentModelMatrix = await viewer.getCameraModelMatrix();
|
|
||||||
Vector3 currentPosition = currentModelMatrix.getTranslation();
|
|
||||||
double distance = currentPosition.length;
|
|
||||||
Quaternion currentRotation =
|
|
||||||
Quaternion.fromRotation(currentModelMatrix.getRotation());
|
|
||||||
|
|
||||||
Quaternion yawRotation = Quaternion.axisAngle(_up, -deltaX);
|
|
||||||
Vector3 right = _up.cross(_forward)..normalize();
|
|
||||||
Quaternion pitchRotation = Quaternion.axisAngle(right, -deltaY);
|
|
||||||
|
|
||||||
Quaternion newRotation = currentRotation * yawRotation * pitchRotation;
|
|
||||||
newRotation.normalize();
|
|
||||||
|
|
||||||
Vector3 newPosition = _forward.clone()
|
|
||||||
..applyQuaternion(newRotation)
|
|
||||||
..scale(-distance);
|
|
||||||
|
|
||||||
Matrix4 newModelMatrix =
|
|
||||||
Matrix4.compose(newPosition, newRotation, Vector3(1, 1, 1));
|
|
||||||
await viewer.setCameraModelMatrix4(newModelMatrix);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@override
|
||||||
|
Future<void> pan(Offset delta, Vector2? velocity) {
|
||||||
|
throw UnimplementedError("Not supported in fixed orbit mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> zoom(double scrollDelta, Vector2? velocity) async {
|
||||||
|
_accumulatedZoomDelta += scrollDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _applyAccumulatedUpdates() async {
|
||||||
|
if (_accumulatedRotationDelta != Offset.zero || _accumulatedZoomDelta != 0.0) {
|
||||||
|
Matrix4 currentModelMatrix = await viewer.getCameraModelMatrix();
|
||||||
|
Vector3 currentPosition = currentModelMatrix.getTranslation();
|
||||||
|
double distance = currentPosition.length;
|
||||||
|
Quaternion currentRotation =
|
||||||
|
Quaternion.fromRotation(currentModelMatrix.getRotation());
|
||||||
|
|
||||||
|
// Apply rotation
|
||||||
|
if (_accumulatedRotationDelta != Offset.zero) {
|
||||||
|
double deltaX = _accumulatedRotationDelta.dx * _rotationSensitivity * viewer.pixelRatio;
|
||||||
|
double deltaY = _accumulatedRotationDelta.dy * _rotationSensitivity * viewer.pixelRatio;
|
||||||
|
|
||||||
|
Quaternion yawRotation = Quaternion.axisAngle(_up, -deltaX);
|
||||||
|
Quaternion pitchRotation = Quaternion.axisAngle(_right, -deltaY);
|
||||||
|
|
||||||
|
currentRotation = currentRotation * yawRotation * pitchRotation;
|
||||||
|
currentRotation.normalize();
|
||||||
|
|
||||||
|
_accumulatedRotationDelta = Offset.zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply zoom
|
||||||
|
if (_accumulatedZoomDelta != 0.0) {
|
||||||
|
var zoomDistance = _zoomCameraDelegate.calculateZoomDistance(
|
||||||
|
_accumulatedZoomDelta,
|
||||||
|
null,
|
||||||
|
Vector3.zero()
|
||||||
|
);
|
||||||
|
distance += zoomDistance;
|
||||||
|
distance = distance.clamp(0.1, 1000.0); // Adjust these limits as needed
|
||||||
|
|
||||||
|
_accumulatedZoomDelta = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate new position
|
||||||
|
Vector3 newPosition = _forward.clone()
|
||||||
|
..applyQuaternion(currentRotation)
|
||||||
|
..scale(-distance);
|
||||||
|
|
||||||
|
// Create and set new model matrix
|
||||||
|
Matrix4 newModelMatrix =
|
||||||
|
Matrix4.compose(newPosition, currentRotation, Vector3(1, 1, 1));
|
||||||
|
await viewer.setCameraModelMatrix4(newModelMatrix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onKeyRelease(PhysicalKeyboardKey key) async {
|
||||||
|
//ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onKeypress(PhysicalKeyboardKey key) async {
|
||||||
|
//ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
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/thermion/widgets/camera/gestures/v2/delegates.dart';
|
||||||
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
|
class FreeFlightCameraDelegate implements CameraDelegate {
|
||||||
|
final ThermionViewer viewer;
|
||||||
|
final bool lockPitch;
|
||||||
|
final bool lockYaw;
|
||||||
|
final bool lockRoll;
|
||||||
|
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<PhysicalKeyboardKey, bool> _pressedKeys = {};
|
||||||
|
|
||||||
|
FreeFlightCameraDelegate(
|
||||||
|
this.viewer, {
|
||||||
|
this.lockPitch = false,
|
||||||
|
this.lockYaw = false,
|
||||||
|
this.lockRoll = false,
|
||||||
|
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<void> _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 = lockYaw ? 0 : _accumulatedRotation.dx * rotationSensitivity * viewer.pixelRatio;
|
||||||
|
double deltaY = lockPitch ? 0 : _accumulatedRotation.dy * rotationSensitivity * viewer.pixelRatio;
|
||||||
|
double deltaZ = lockRoll ? 0 : (_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 * yawRotation * pitchRotation * rollRotation;
|
||||||
|
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<void> rotate(Offset delta, Vector2? velocity) async {
|
||||||
|
_accumulatedRotation += delta;
|
||||||
|
_lastVelocity = velocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> pan(Offset delta, Vector2? velocity) async {
|
||||||
|
_accumulatedPan += delta;
|
||||||
|
_lastVelocity = velocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> zoom(double scrollDelta, Vector2? velocity) async {
|
||||||
|
_accumulatedZoom += scrollDelta;
|
||||||
|
_lastVelocity = velocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onKeypress(PhysicalKeyboardKey key) async {
|
||||||
|
_pressedKeys[key] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onKeyRelease(PhysicalKeyboardKey key) async {
|
||||||
|
_pressedKeys.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _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<void> _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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user