refactor InputHandler interface/DelegateInputHandler implementation

This commit is contained in:
Nick Fisher
2025-05-09 11:18:07 +08:00
parent 7961ed06f7
commit 1ddeac2d31
13 changed files with 1470 additions and 1058 deletions

View File

@@ -1,7 +1,7 @@
library;
export 'src/input_types.dart';
export 'src/input_handler.dart';
export 'src/delegates.dart';
export 'src/delegate_input_handler.dart';
export 'src/implementations/default_pick_delegate.dart';
export 'src/implementations/gizmo_pick_delegate.dart';

View File

@@ -1,301 +1,99 @@
import 'dart:async';
import 'package:logging/logging.dart';
import 'package:thermion_dart/src/input/src/implementations/fixed_orbit_camera_delegate_v2.dart';
import 'package:thermion_dart/src/input/src/implementations/free_flight_camera_delegate_v2.dart';
import 'package:thermion_dart/thermion_dart.dart';
import 'implementations/fixed_orbit_camera_rotation_delegate.dart';
import 'implementations/free_flight_camera_delegate.dart';
typedef PointerEventDetails = (Vector2 localPosition, Vector2 delta);
abstract class InputHandlerDelegate {
Future handle(Set<InputEvent> events);
}
///
/// An [InputHandler] that accumulates pointer/key events every frame,
/// delegating the actual update to an [InputHandlerDelegate].
///
class DelegateInputHandler implements InputHandler {
final ThermionViewer viewer;
Stream<List<InputType>> get gestures => _gesturesController.stream;
final _gesturesController = StreamController<List<InputType>>.broadcast();
late final _logger = Logger(this.runtimeType.toString());
Stream<Matrix4> get cameraUpdated => _cameraUpdatedController.stream;
final _cameraUpdatedController = StreamController<Matrix4>.broadcast();
Stream<List<InputEvent>> get events => _gesturesController.stream;
final _logger = Logger("DelegateInputHandler");
InputHandlerDelegate? transformDelegate;
PickDelegate? pickDelegate;
final Set<PhysicalKey> _pressedKeys = {};
final _inputDeltas = <InputType, Vector3>{};
Map<InputType, InputAction> _actions = {
InputType.LMB_HOLD_AND_MOVE: InputAction.TRANSLATE,
InputType.SCALE1: InputAction.TRANSLATE,
InputType.SCALE2: InputAction.ZOOM,
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 = <InputType, Matrix3>{};
void setTransformForAction(InputType inputType, Matrix3 transform) {
_axes[inputType] = transform;
}
DelegateInputHandler({
required this.viewer,
required this.transformDelegate,
this.pickDelegate,
Map<InputType, InputAction>? actions,
}) {
if (actions != null) {
_actions = actions;
}
if (pickDelegate != null) {
if (_actions[InputType.LMB_DOWN] != null) {
throw Exception();
}
_actions[InputType.LMB_DOWN] = InputAction.PICK;
}
for (var gestureType in InputType.values) {
_inputDeltas[gestureType] = Vector3.zero();
}
final _gesturesController = StreamController<List<InputEvent>>.broadcast();
final _events = <InputEvent>{};
final List<InputHandlerDelegate> delegates;
DelegateInputHandler({required this.viewer, required this.delegates}) {
FilamentApp.instance!.registerRequestFrameHook(process);
}
factory DelegateInputHandler.fixedOrbit(ThermionViewer viewer,
{double minimumDistance = 10.0,
Vector3? target,
ThermionEntity? entity,
PickDelegate? pickDelegate}) =>
DelegateInputHandler(
viewer: viewer,
pickDelegate: pickDelegate,
transformDelegate: FixedOrbitRotateInputHandlerDelegate(viewer.view,
minimumDistance: minimumDistance),
actions: {
InputType.MMB_HOLD_AND_MOVE: InputAction.ROTATE,
InputType.SCALE1: InputAction.ROTATE,
InputType.SCALE2: InputAction.ZOOM,
InputType.SCROLLWHEEL: InputAction.ZOOM
});
{double minimumDistance = 0.1,
Vector3? target,
InputSensitivityOptions sensitivity = const InputSensitivityOptions(),
ThermionEntity? entity}) {
return DelegateInputHandler(viewer: viewer, delegates: [
OrbitInputHandlerDelegate(viewer.view,
sensitivity: sensitivity,
minZoomDistance: minimumDistance,
maxZoomDistance: 1000.0)
]);
}
factory DelegateInputHandler.flight(ThermionViewer viewer,
{PickDelegate? pickDelegate,
bool freeLook = false,
double panSensitivity = 0.1,
double zoomSensitivity = 0.1,
double movementSensitivity = 0.1,
double rotateSensitivity = 0.01,
double? clampY,
{bool freeLook = false,
InputSensitivityOptions sensitivity = const InputSensitivityOptions(),
ThermionEntity? entity}) =>
DelegateInputHandler(
viewer: viewer,
pickDelegate: pickDelegate,
transformDelegate: FreeFlightInputHandlerDelegate(viewer.view,
clampY: clampY,
rotationSensitivity: rotateSensitivity,
zoomSensitivity: zoomSensitivity,
panSensitivity: panSensitivity,
movementSensitivity: movementSensitivity),
actions: {
InputType.MMB_HOLD_AND_MOVE: InputAction.ROTATE,
InputType.SCROLLWHEEL: InputAction.ZOOM,
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,
InputType.SCALE1: InputAction.TRANSLATE,
InputType.SCALE2: InputAction.ZOOM,
if (freeLook) InputType.POINTER_MOVE: InputAction.ROTATE,
});
DelegateInputHandler(viewer: viewer, delegates: [
FreeFlightInputHandlerDelegateV2(viewer.view, sensitivity: sensitivity)
]);
bool _processing = false;
Future<void> 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);
}
final keyTypes = <InputType>[];
for (final key in _pressedKeys) {
InputAction? keyAction;
InputType? keyType = null;
Vector3? vector;
final delegate = delegates.first;
final keyUp = <PhysicalKey, KeyEvent>{};
final keyDown = <PhysicalKey, KeyEvent>{};
switch (key) {
case PhysicalKey.W:
keyType = InputType.KEYDOWN_W;
vector = Vector3(0, 0, -1);
break;
case PhysicalKey.A:
keyType = InputType.KEYDOWN_A;
vector = Vector3(-1, 0, 0);
break;
case PhysicalKey.S:
keyType = InputType.KEYDOWN_S;
vector = Vector3(0, 0, 1);
break;
case PhysicalKey.D:
keyType = InputType.KEYDOWN_D;
vector = Vector3(1, 0, 0);
break;
}
// ignore: unnecessary_null_comparison
if (keyType != null) {
keyAction = _actions[keyType];
if (keyAction != null) {
var transform = _axes[keyAction];
if (transform != null) {
vector = transform * vector;
}
transformDelegate?.queue(keyAction, vector!);
keyTypes.add(keyType);
for (final event in _events) {
if (event is KeyEvent) {
switch (event.type) {
case KeyEventType.up:
keyUp[event.key] = event;
case KeyEventType.down:
keyDown[event.key] = event;
}
}
}
}
for (final key in keyUp.keys) {
_events.remove(keyDown[key]);
_events.remove(keyUp[key]);
}
var transform = await transformDelegate?.execute();
var updates = _inputDeltas.keys.followedBy(keyTypes).toList();
if (updates.isNotEmpty) {
_gesturesController.add(updates);
}
if (transform != null) {
_cameraUpdatedController.add(transform);
}
await delegate.handle(_events);
_inputDeltas.clear();
_events.clear();
_events.addAll(keyDown.values);
_processing = false;
}
@override
Future<void> 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<void> 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);
}
}
@override
Future<void> onPointerUp(bool isMiddle) async {}
@override
Future<void> 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<void> 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 {
FilamentApp.instance!.unregisterRequestFrameHook(process);
}
@override
Future<bool> get initialized => viewer.initialized;
@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);
}
@override
Future<void> onScaleEnd(int pointerCount, double velocity) async {}
@override
Future<void> onScaleStart(Vector2 localPosition, int pointerCount,
Duration? sourceTimestamp) async {
// noop
}
@override
Future<void> onScaleUpdate(
Vector2 focalPoint,
Vector2 focalPointDelta,
double horizontalScale,
double verticalScale,
double scale,
int pointerCount,
double rotation,
Duration? sourceTimestamp) async {
if (pointerCount == 1) {
_inputDeltas[InputType.SCALE1] =
Vector3(focalPointDelta.x, focalPointDelta.y, 0);
} else if (pointerCount == 2) {
_inputDeltas[InputType.SCALE2] = Vector3(0, 0, scale);
} else {
throw UnimplementedError("Only pointerCount <= 2 supported");
Future handle(InputEvent event) async {
if (_processing) {
return;
}
}
@override
Stream<Matrix4> get transformUpdated => cameraUpdated;
_events.add(event);
}
}

View File

@@ -1,28 +0,0 @@
import 'package:vector_math/vector_math_64.dart';
import 'input_handler.dart';
abstract class InputHandlerDelegate {
Future queue(InputAction action, Vector3? delta);
Future<Matrix4?> 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);
Future dispose();
}

View File

@@ -0,0 +1,228 @@
import 'dart:async';
import 'dart:math' as math;
import 'package:vector_math/vector_math_64.dart';
import '../../../viewer/viewer.dart';
import '../../input.dart';
class CustomInputHandlerDelegate implements InputHandlerDelegate {
final View view;
final ThermionAsset asset;
final InputSensitivityOptions sensitivity;
final Vector3 targetPoint;
final double minZoomDistance;
final double maxZoomDistance;
final worldUp = Vector3(0, 1, 0);
double _radius;
double _radiusScaleFactor = 1.0;
double _azimuth; // Angle around worldUp (Y-axis), in radians
double
_elevation; // Angle above the XZ plane (around local X-axis), in radians
bool _isInitialized = false;
bool _isMouseDown = false;
Vector2? _lastPointerPosition;
CustomInputHandlerDelegate(
this.view, this.asset, {
this.sensitivity = const InputSensitivityOptions(),
Vector3? targetPoint,
this.minZoomDistance = 1.0,
this.maxZoomDistance = 100.0,
}) : targetPoint = targetPoint ?? Vector3.zero(),
_radius =
(minZoomDistance + maxZoomDistance) / 2, // Initial default radius
_azimuth = 0.0,
_elevation = math.pi / 4; // Initial default elevation (45 degrees)
Future<void> _initializeFromCamera(Camera activeCamera) async {
final currentModelMatrix = await activeCamera.getModelMatrix();
final cameraPosition = currentModelMatrix.getTranslation();
final directionToCamera = cameraPosition - targetPoint;
_radius = directionToCamera.length;
_radius = _radius.clamp(minZoomDistance, maxZoomDistance);
if (_radius < 0.001) {
_radius = minZoomDistance;
_azimuth = 0.0;
_elevation = math.pi / 4;
} else {
final dirToCameraNormalized = directionToCamera.normalized();
// Elevation: angle with the XZ plane (plane perpendicular to worldUp)
// Assuming worldUp is (0,1,0), elevation is asin(y)
_elevation = math.asin(dirToCameraNormalized.dot(worldUp));
// Azimuth: angle in the XZ plane.
// Project dirToCameraNormalized onto the plane perpendicular to worldUp
Vector3 projectionOnPlane =
(dirToCameraNormalized - worldUp * math.sin(_elevation)).normalized();
if (projectionOnPlane.length2 < 0.0001 &&
worldUp.dot(Vector3(0, 0, 1)).abs() < 0.99) {
// looking straight up/down, pick a default reference for azimuth
projectionOnPlane =
Vector3(0, 0, 1); // if worldUp is Y, project onto XZ plane
} else if (projectionOnPlane.length2 < 0.0001) {
// if worldUp is Z, project onto XY plane
projectionOnPlane = Vector3(1, 0, 0);
}
// Define a reference vector in the plane (e.g., world X-axis or Z-axis)
// Let's use world Z-axis as the 0-azimuth reference, if not aligned with worldUp
Vector3 referenceAzimuthVector = Vector3(0, 0, 1);
if (worldUp.dot(referenceAzimuthVector).abs() > 0.99) {
// If worldUp is Z, use X instead
referenceAzimuthVector = Vector3(1, 0, 0);
}
// Ensure referenceAzimuthVector is also in the plane
referenceAzimuthVector = (referenceAzimuthVector -
worldUp * referenceAzimuthVector.dot(worldUp))
.normalized();
_azimuth = math.atan2(
projectionOnPlane.cross(referenceAzimuthVector).dot(worldUp),
projectionOnPlane.dot(referenceAzimuthVector));
}
_elevation = _elevation.clamp(
-math.pi / 2 + 0.01, math.pi / 2 - 0.01); // Clamp elevation
_isInitialized = true;
}
@override
Future<void> handle(Set<InputEvent> events) async {
final activeCamera = await view.getCamera();
if (!_isInitialized) {
await _initializeFromCamera(activeCamera);
}
double deltaAzimuth = 0;
double deltaElevation = 0;
double deltaRadius = 0;
for (final event in events) {
switch (event) {
case ScrollEvent(delta: final scrollDelta):
deltaRadius += sensitivity.scrollWheelSensitivity * scrollDelta;
break;
case MouseEvent(
type: final type,
button: final button,
localPosition: final localPosition,
// delta: final mouseDelta // Using localPosition to calculate delta from _lastPointerPosition
):
switch (type) {
case MouseEventType.buttonDown:
if (button == MouseButton.left) {
// Typically left mouse button for orbit
_isMouseDown = true;
_lastPointerPosition = localPosition;
}
break;
case MouseEventType.buttonUp:
if (button == MouseButton.left) {
_isMouseDown = false;
_lastPointerPosition = null;
}
break;
case MouseEventType.move:
case MouseEventType
.hover: // Some systems might only send hover when no buttons pressed
if (_isMouseDown && _lastPointerPosition != null) {
final dragDelta = localPosition - _lastPointerPosition!;
// X-drag affects azimuth, Y-drag affects elevation
deltaAzimuth -= dragDelta.x *
sensitivity.mouseSensitivity; // Invert X for natural feel
deltaElevation -= dragDelta.y *
sensitivity.mouseSensitivity; // Invert Y for natural feel
_lastPointerPosition = localPosition;
} else if (type == MouseEventType.hover) {
// Allow hover to set initial if not dragging
_lastPointerPosition = localPosition;
}
break;
}
break;
case TouchEvent(
type: final type,
localPosition: final localPosition,
delta: final touchDelta,
):
switch (type) {
case TouchEventType.tap:
break;
default:
break;
}
break;
case ScaleUpdateEvent(
numPointers: final numPointers,
scale: final scaleFactor,
localFocalPoint: final localFocalPoint,
localFocalPointDelta: final localFocalPointDelta
):
if (numPointers == 1) {
deltaAzimuth -=
localFocalPointDelta!.$1 * sensitivity.touchSensitivity;
deltaElevation -=
localFocalPointDelta.$2 * sensitivity.touchSensitivity;
} else {
_radiusScaleFactor = scaleFactor;
}
case ScaleEndEvent():
_radius *= _radiusScaleFactor;
_radiusScaleFactor = 1.0;
default:
break;
}
}
if (deltaAzimuth == 0 &&
deltaElevation == 0 &&
deltaRadius == 0 &&
_radiusScaleFactor == 1.0) {
return;
}
_azimuth += deltaAzimuth;
_elevation += deltaElevation;
_radius += deltaRadius;
var radius = _radius * _radiusScaleFactor;
// Clamp parameters
_elevation = _elevation.clamp(-math.pi / 2 + 0.01,
math.pi / 2 - 0.01); // Prevent gimbal lock at poles
radius = radius.clamp(minZoomDistance, maxZoomDistance);
_azimuth =
_azimuth % (2 * math.pi); // Keep azimuth within 0-2PI range (optional)
final double xOffset = radius * math.cos(_elevation) * math.sin(_azimuth);
final double yOffset = radius * math.sin(_elevation);
final double zOffset = radius * math.cos(_elevation) * math.cos(_azimuth);
Vector3 cameraPosition;
if (worldUp.dot(Vector3(0, 1, 0)).abs() > 0.99) {
// Standard Y-up
cameraPosition = targetPoint + Vector3(xOffset, yOffset, zOffset);
} else if (worldUp.dot(Vector3(0, 0, 1)).abs() > 0.99) {
cameraPosition = targetPoint +
Vector3(
radius * math.cos(_elevation) * math.cos(_azimuth), // x
radius * math.cos(_elevation) * math.sin(_azimuth), // y
radius * math.sin(_elevation) // z
);
} else {
cameraPosition = targetPoint + Vector3(xOffset, yOffset, zOffset);
}
final modelMatrix = makeViewMatrix(cameraPosition, targetPoint, worldUp)
..invert();
await activeCamera.setModelMatrix(modelMatrix);
}
}

View File

@@ -1,24 +1,24 @@
import 'dart:async';
// import 'dart:async';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:vector_math/vector_math_64.dart';
// import 'package:thermion_dart/thermion_dart.dart';
// import 'package:vector_math/vector_math_64.dart';
class DefaultPickDelegate extends PickDelegate {
final ThermionViewer viewer;
// class DefaultPickDelegate extends PickDelegate {
// final ThermionViewer viewer;
DefaultPickDelegate(this.viewer);
// DefaultPickDelegate(this.viewer);
final _picked = StreamController<ThermionEntity>();
Stream<ThermionEntity> get picked => _picked.stream;
// final _picked = StreamController<ThermionEntity>();
// Stream<ThermionEntity> get picked => _picked.stream;
Future dispose() async {
_picked.close();
}
// Future dispose() async {
// _picked.close();
// }
@override
void pick(Vector2 location) {
viewer.view.pick(location.x.toInt(), location.y.toInt(), (result) {
_picked.sink.add(result.entity);
});
}
}
// @override
// void pick(Vector2 location) {
// viewer.view.pick(location.x.toInt(), location.y.toInt(), (result) {
// _picked.sink.add(result.entity);
// });
// }
// }

View File

@@ -0,0 +1,235 @@
import 'dart:async';
import 'dart:math' as math;
import 'package:vector_math/vector_math_64.dart';
import '../../../viewer/viewer.dart';
import '../../input.dart';
class OrbitInputHandlerDelegate implements InputHandlerDelegate {
final View view;
final InputSensitivityOptions sensitivity;
final Vector3 targetPoint;
final double minZoomDistance;
final double maxZoomDistance;
final worldUp = Vector3(0, 1, 0);
double _radius;
double _radiusScaleFactor = 1.0;
double _azimuth; // Angle around worldUp (Y-axis), in radians
double
_elevation; // Angle above the XZ plane (around local X-axis), in radians
bool _isInitialized = false;
bool _isMouseDown = false;
Vector2? _lastPointerPosition;
OrbitInputHandlerDelegate(
this.view, {
this.sensitivity = const InputSensitivityOptions(),
Vector3? targetPoint,
this.minZoomDistance = 1.0,
this.maxZoomDistance = 100.0,
}) : targetPoint = targetPoint ?? Vector3.zero(),
_radius =
(minZoomDistance + maxZoomDistance) / 2, // Initial default radius
_azimuth = 0.0,
_elevation = math.pi / 4; // Initial default elevation (45 degrees)
Future<void> _initializeFromCamera(Camera activeCamera) async {
final currentModelMatrix = await activeCamera.getModelMatrix();
final cameraPosition = currentModelMatrix.getTranslation();
final directionToCamera = cameraPosition - targetPoint;
_radius = directionToCamera.length;
_radius = _radius.clamp(minZoomDistance, maxZoomDistance);
if (_radius < 0.001) {
_radius = minZoomDistance;
_azimuth = 0.0;
_elevation = math.pi / 4;
} else {
final dirToCameraNormalized = directionToCamera.normalized();
// Elevation: angle with the XZ plane (plane perpendicular to worldUp)
// Assuming worldUp is (0,1,0), elevation is asin(y)
_elevation = math.asin(dirToCameraNormalized.dot(worldUp));
// Azimuth: angle in the XZ plane.
// Project dirToCameraNormalized onto the plane perpendicular to worldUp
Vector3 projectionOnPlane =
(dirToCameraNormalized - worldUp * math.sin(_elevation)).normalized();
if (projectionOnPlane.length2 < 0.0001 &&
worldUp.dot(Vector3(0, 0, 1)).abs() < 0.99) {
// looking straight up/down, pick a default reference for azimuth
projectionOnPlane =
Vector3(0, 0, 1); // if worldUp is Y, project onto XZ plane
} else if (projectionOnPlane.length2 < 0.0001) {
// if worldUp is Z, project onto XY plane
projectionOnPlane = Vector3(1, 0, 0);
}
// Define a reference vector in the plane (e.g., world X-axis or Z-axis)
// Let's use world Z-axis as the 0-azimuth reference, if not aligned with worldUp
Vector3 referenceAzimuthVector = Vector3(0, 0, 1);
if (worldUp.dot(referenceAzimuthVector).abs() > 0.99) {
// If worldUp is Z, use X instead
referenceAzimuthVector = Vector3(1, 0, 0);
}
// Ensure referenceAzimuthVector is also in the plane
referenceAzimuthVector = (referenceAzimuthVector -
worldUp * referenceAzimuthVector.dot(worldUp))
.normalized();
_azimuth = math.atan2(
projectionOnPlane.cross(referenceAzimuthVector).dot(worldUp),
projectionOnPlane.dot(referenceAzimuthVector));
}
_elevation = _elevation.clamp(
-math.pi / 2 + 0.01, math.pi / 2 - 0.01); // Clamp elevation
_isInitialized = true;
}
@override
Future<void> handle(Set<InputEvent> events) async {
final activeCamera = await view.getCamera();
if (!_isInitialized) {
await _initializeFromCamera(activeCamera);
}
double deltaAzimuth = 0;
double deltaElevation = 0;
double deltaRadius = 0;
for (final event in events) {
switch (event) {
case ScrollEvent(delta: final scrollDelta):
deltaRadius += sensitivity.scrollWheelSensitivity * scrollDelta;
break;
case MouseEvent(
type: final type,
button: final button,
localPosition: final localPosition,
// delta: final mouseDelta // Using localPosition to calculate delta from _lastPointerPosition
):
switch (type) {
case MouseEventType.buttonDown:
if (button == MouseButton.left) {
// Typically left mouse button for orbit
_isMouseDown = true;
_lastPointerPosition = localPosition;
}
break;
case MouseEventType.buttonUp:
if (button == MouseButton.left) {
_isMouseDown = false;
_lastPointerPosition = null;
}
break;
case MouseEventType.move:
case MouseEventType
.hover: // Some systems might only send hover when no buttons pressed
if (_isMouseDown && _lastPointerPosition != null) {
final dragDelta = localPosition - _lastPointerPosition!;
// X-drag affects azimuth, Y-drag affects elevation
deltaAzimuth -= dragDelta.x *
sensitivity.mouseSensitivity; // Invert X for natural feel
deltaElevation -= dragDelta.y *
sensitivity.mouseSensitivity; // Invert Y for natural feel
_lastPointerPosition = localPosition;
} else if (type == MouseEventType.hover) {
// Allow hover to set initial if not dragging
_lastPointerPosition = localPosition;
}
break;
}
break;
case TouchEvent(
type: final type,
localPosition: final localPosition,
delta: final touchDelta,
):
switch (type) {
case TouchEventType.tap:
break;
default:
break;
}
break;
case ScaleUpdateEvent(
numPointers: final numPointers,
scale: final scaleFactor,
localFocalPoint: final localFocalPoint,
localFocalPointDelta: final localFocalPointDelta
):
if (numPointers == 1) {
deltaAzimuth -= localFocalPointDelta!.$1 * sensitivity.touchSensitivity;
deltaElevation -= localFocalPointDelta.$2 * sensitivity.touchSensitivity;
} else {
_radiusScaleFactor = scaleFactor;
}
case ScaleEndEvent():
_radius *= _radiusScaleFactor;
_radiusScaleFactor = 1.0;
default:
break;
}
}
if (deltaAzimuth == 0 &&
deltaElevation == 0 &&
deltaRadius == 0 &&
_radiusScaleFactor == 1.0) {
return;
}
_azimuth += deltaAzimuth;
_elevation += deltaElevation;
_radius += deltaRadius;
var radius = _radius * _radiusScaleFactor;
// Clamp parameters
_elevation = _elevation.clamp(-math.pi / 2 + 0.01,
math.pi / 2 - 0.01); // Prevent gimbal lock at poles
radius = radius.clamp(minZoomDistance, maxZoomDistance);
_azimuth =
_azimuth % (2 * math.pi); // Keep azimuth within 0-2PI range (optional)
final double xOffset = radius * math.cos(_elevation) * math.sin(_azimuth);
final double yOffset = radius * math.sin(_elevation);
final double zOffset = radius * math.cos(_elevation) * math.cos(_azimuth);
Vector3 cameraPosition;
if (worldUp.dot(Vector3(0, 1, 0)).abs() > 0.99) {
// Standard Y-up
cameraPosition = targetPoint + Vector3(xOffset, yOffset, zOffset);
} else if (worldUp.dot(Vector3(0, 0, 1)).abs() > 0.99) {
cameraPosition = targetPoint +
Vector3(
radius * math.cos(_elevation) * math.cos(_azimuth), // x
radius * math.cos(_elevation) * math.sin(_azimuth), // y
radius * math.sin(_elevation) // z
);
} else {
cameraPosition = targetPoint + Vector3(xOffset, yOffset, zOffset);
}
final modelMatrix = makeViewMatrix(cameraPosition, targetPoint, worldUp)
..invert();
await activeCamera.setModelMatrix(modelMatrix);
}
}
// _lastPointerPosition =
// localFocalPoint;
// } else if (_isPointerDown && _lastPointerPosition != null) {
// final currentDragDelta = localPosition! - _lastPointerPosition!;
// deltaAzimuth -=
// currentDragDelta.x * sensitivity.touchSensitivity;
// deltaElevation -=
// currentDragDelta.y * sensitivity.touchSensitivity;
// _lastPointerPosition = localPosition;
// }

View File

@@ -1,142 +1,143 @@
import 'dart:async';
import 'package:vector_math/vector_math_64.dart';
// import 'dart:async';
// import 'package:vector_math/vector_math_64.dart';
import '../../../viewer/viewer.dart';
import '../../input.dart';
// import '../../../viewer/viewer.dart';
// import '../../input.dart';
///
/// An [InputHandlerDelegate] that orbits the camera around a fixed
/// point.
///
class FixedOrbitRotateInputHandlerDelegate implements InputHandlerDelegate {
final View view;
final double minimumDistance;
late final Vector3 target;
// ///
// /// An [InputHandlerDelegate] that orbits the camera around a fixed
// /// point.
// ///
// class FixedOrbitRotateInputHandlerDelegate implements InputHandlerDelegate {
// final View view;
// final double minimumDistance;
// late final Vector3 target;
final double rotationSensitivity;
final double zoomSensitivity;
// final double rotationSensitivity;
// final double zoomSensitivity;
Vector2 _queuedRotationDelta = Vector2.zero();
double _queuedZoomDelta = 0.0;
// Vector2 _queuedRotationDelta = Vector2.zero();
// double _queuedZoomDelta = 0.0;
Timer? _updateTimer;
// Timer? _updateTimer;
FixedOrbitRotateInputHandlerDelegate(
this.view, {
Vector3? target,
this.minimumDistance = 10.0,
this.rotationSensitivity = 0.01,
this.zoomSensitivity = 0.1,
}) {
this.target = target ?? Vector3.zero();
// FixedOrbitRotateInputHandlerDelegate(
// this.view, {
// Vector3? target,
// this.minimumDistance = 10.0,
// this.rotationSensitivity = 0.01,
// this.zoomSensitivity = 0.1,
// }) {
// this.target = target ?? Vector3.zero();
view.getCamera().then((camera) {
camera.lookAt(Vector3(0.0, 0, -minimumDistance),
focus: this.target, up: Vector3(0.0, 1.0, 0.0));
});
}
// view.getCamera().then((camera) {
// camera.lookAt(Vector3(0.0, 0, -minimumDistance),
// focus: this.target, up: Vector3(0.0, 1.0, 0.0));
// });
// }
void dispose() {
_updateTimer?.cancel();
}
// void dispose() {
// _updateTimer?.cancel();
// }
@override
Future<void> queue(InputAction action, Vector3? delta) async {
if (delta == null) return;
// @override
// Future<void> 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:
break;
case InputAction.NONE:
// Do nothing
break;
case InputAction.ZOOM:
_queuedZoomDelta += delta.z;
break;
}
}
// switch (action) {
// case InputAction.ROTATE:
// _queuedRotationDelta += Vector2(delta.x, delta.y);
// break;
// case InputAction.TRANSLATE:
// _queuedZoomDelta += delta.z;
// break;
// case InputAction.PICK:
// break;
// case InputAction.NONE:
// // Do nothing
// break;
// case InputAction.ZOOM:
// _queuedZoomDelta -= (delta.z - 1.0);
// break;
// }
// }
bool _executing = false;
// bool _executing = false;
@override
Future<Matrix4?> execute() async {
if (_queuedRotationDelta.length2 == 0.0 && _queuedZoomDelta == 0.0) {
return null;
}
// @override
// Future<Matrix4?> execute() async {
// if (_queuedRotationDelta.length2 == 0.0 && _queuedZoomDelta == 0.0) {
// return null;
// }
if (_executing) {
return null;
}
// if (_executing) {
// return null;
// }
_executing = true;
// _executing = true;
final camera = await view.getCamera();
// final camera = await view.getCamera();
var modelMatrix = await camera.getModelMatrix();
Vector3 currentPosition = modelMatrix.getTranslation();
// var modelMatrix = await camera.getModelMatrix();
Vector3 forward = modelMatrix.forward;
// Vector3 currentPosition = modelMatrix.getTranslation();
if (forward.length == 0) {
forward = Vector3(0, 0, -1);
currentPosition = Vector3(0, 0, minimumDistance);
}
// Vector3 forward = modelMatrix.forward;
Matrix4? updatedModelMatrix = null;
// if (forward.length == 0) {
// forward = Vector3(0, 0, -1);
// currentPosition = Vector3(0, 0, minimumDistance);
// }
// Zoom
if (_queuedZoomDelta != 0.0) {
var newPosition = currentPosition +
(currentPosition - target).scaled(_queuedZoomDelta * zoomSensitivity);
// Matrix4? updatedModelMatrix = null;
var distToTarget = (newPosition - target).length;
// // Zoom
// if (_queuedZoomDelta != 0.0) {
// print("_queuedZoomDelta $_queuedZoomDelta");
// var newPosition = currentPosition +
// (currentPosition - target).scaled(_queuedZoomDelta * zoomSensitivity);
// if we somehow overshot the minimum distance, reset the camera to the minimum distance
if (distToTarget >= minimumDistance) {
currentPosition = newPosition;
// Calculate view matrix
forward = (currentPosition - target).normalized();
var right = modelMatrix.up.cross(forward).normalized();
var up = forward.cross(right);
// var distToTarget = (newPosition - target).length;
Matrix4 newViewMatrix = makeViewMatrix(currentPosition, target, up);
newViewMatrix.invert();
// // if we somehow overshot the minimum distance, reset the camera to the minimum distance
// if (distToTarget >= minimumDistance) {
// currentPosition = newPosition;
// // Calculate view matrix
// forward = (currentPosition - target).normalized();
// var right = modelMatrix.up.cross(forward).normalized();
// var up = forward.cross(right);
await camera.setModelMatrix(newViewMatrix);
updatedModelMatrix = newViewMatrix;
}
} else if (_queuedRotationDelta.length != 0) {
double rotateX = _queuedRotationDelta.x * rotationSensitivity;
double rotateY = _queuedRotationDelta.y * rotationSensitivity;
// Matrix4 newViewMatrix = makeViewMatrix(currentPosition, target, up);
// newViewMatrix.invert();
var modelMatrix = await camera.getModelMatrix();
// await camera.setModelMatrix(newViewMatrix);
// updatedModelMatrix = newViewMatrix;
// }
// } else if (_queuedRotationDelta.length != 0) {
// double rotateX = _queuedRotationDelta.x * rotationSensitivity;
// double rotateY = _queuedRotationDelta.y * rotationSensitivity;
// for simplicity, we always assume a fixed coordinate system where
// we are rotating around world Y and camera X
var rot1 = Matrix4.identity()
..setRotation(Quaternion.axisAngle(Vector3(0, 1, 0), -rotateX)
.asRotationMatrix());
var rot2 = Matrix4.identity()
..setRotation(Quaternion.axisAngle(modelMatrix.right, rotateY)
.asRotationMatrix());
// var modelMatrix = await camera.getModelMatrix();
modelMatrix = rot1 * rot2 * modelMatrix;
await camera.setModelMatrix(modelMatrix);
updatedModelMatrix = modelMatrix;
}
// // for simplicity, we always assume a fixed coordinate system where
// // we are rotating around world Y and camera X
// var rot1 = Matrix4.identity()
// ..setRotation(Quaternion.axisAngle(Vector3(0, 1, 0), -rotateX)
// .asRotationMatrix());
// var rot2 = Matrix4.identity()
// ..setRotation(Quaternion.axisAngle(modelMatrix.right, rotateY)
// .asRotationMatrix());
// Reset queued deltas
_queuedRotationDelta = Vector2.zero();
_queuedZoomDelta = 0.0;
// modelMatrix = rot1 * rot2 * modelMatrix;
// await camera.setModelMatrix(modelMatrix);
// updatedModelMatrix = modelMatrix;
// }
_executing = false;
return updatedModelMatrix;
}
}
// // Reset queued deltas
// _queuedRotationDelta = Vector2.zero();
// _queuedZoomDelta = 0.0;
// _executing = false;
// return updatedModelMatrix;
// }
// }

View File

@@ -1,133 +1,133 @@
import 'dart:async';
import 'package:vector_math/vector_math_64.dart';
import '../../../viewer/viewer.dart';
import '../delegates.dart';
import '../input_handler.dart';
// import 'dart:async';
// import 'package:vector_math/vector_math_64.dart';
// import '../../../viewer/viewer.dart';
// import '../../input.dart';
class FreeFlightInputHandlerDelegate implements InputHandlerDelegate {
final View view;
// class FreeFlightInputHandlerDelegate implements InputHandlerDelegate {
// final View view;
final Vector3? minBounds;
final Vector3? maxBounds;
final double rotationSensitivity;
final double movementSensitivity;
final double zoomSensitivity;
final double panSensitivity;
final double? clampY;
// final Vector3? minBounds;
// final Vector3? maxBounds;
// final double rotationSensitivity;
// final double movementSensitivity;
// final double zoomSensitivity;
// final double panSensitivity;
// final double? clampY;
Vector2 _queuedRotationDelta = Vector2.zero();
Vector3 _queuedTranslateDelta = Vector3.zero();
double _queuedZoomDelta = 0.0;
Vector3 _queuedMoveDelta = Vector3.zero();
// Vector2 _queuedRotationDelta = Vector2.zero();
// Vector3 _queuedTranslateDelta = Vector3.zero();
// double _queuedZoomDelta = 0.0;
// Vector3 _queuedMoveDelta = Vector3.zero();
FreeFlightInputHandlerDelegate(this.view,
{this.minBounds,
this.maxBounds,
this.rotationSensitivity = 0.001,
this.movementSensitivity = 0.1,
this.zoomSensitivity = 0.1,
this.panSensitivity = 0.1,
this.clampY}) {}
// FreeFlightInputHandlerDelegate(this.view,
// {this.minBounds,
// this.maxBounds,
// this.rotationSensitivity = 0.001,
// this.movementSensitivity = 0.1,
// this.zoomSensitivity = 0.1,
// this.panSensitivity = 0.1,
// this.clampY}) {}
@override
Future<void> queue(InputAction action, Vector3? delta) async {
if (delta == null) return;
// @override
// Future<void> 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:
_queuedTranslateDelta += delta;
break;
case InputAction.PICK:
_queuedZoomDelta += delta.z;
break;
case InputAction.NONE:
break;
case InputAction.ZOOM:
_queuedZoomDelta += delta.z;
break;
}
}
// switch (action) {
// case InputAction.ROTATE:
// _queuedRotationDelta += Vector2(delta.x, delta.y);
// break;
// case InputAction.TRANSLATE:
// _queuedTranslateDelta += delta;
// break;
// case InputAction.PICK:
// _queuedZoomDelta += delta.z;
// break;
// case InputAction.NONE:
// break;
// case InputAction.ZOOM:
// _queuedZoomDelta += delta.z;
// break;
// }
// }
bool _executing = false;
// bool _executing = false;
@override
Future<Matrix4?> execute() async {
if (_executing) {
return null;
}
// @override
// Future<Matrix4?> execute() async {
// if (_executing) {
// return null;
// }
_executing = true;
// _executing = true;
if (_queuedRotationDelta.length2 == 0.0 &&
_queuedTranslateDelta.length2 == 0.0 &&
_queuedZoomDelta == 0.0 &&
_queuedMoveDelta.length2 == 0.0) {
_executing = false;
return null;
}
// if (_queuedRotationDelta.length2 == 0.0 &&
// _queuedTranslateDelta.length2 == 0.0 &&
// _queuedZoomDelta == 0.0 &&
// _queuedMoveDelta.length2 == 0.0) {
// _executing = false;
// return null;
// }
final activeCamera = await view.getCamera();
// final activeCamera = await view.getCamera();
Matrix4 current = await activeCamera.getModelMatrix();
// Matrix4 current = await activeCamera.getModelMatrix();
Vector3 relativeTranslation = Vector3.zero();
Quaternion relativeRotation = Quaternion.identity();
// Vector3 relativeTranslation = Vector3.zero();
// Quaternion relativeRotation = Quaternion.identity();
if (_queuedRotationDelta.length2 > 0.0) {
double deltaX = _queuedRotationDelta.x * rotationSensitivity;
double deltaY = _queuedRotationDelta.y * rotationSensitivity;
relativeRotation = Quaternion.axisAngle(current.up, -deltaX) *
Quaternion.axisAngle(current.right, -deltaY);
_queuedRotationDelta = Vector2.zero();
}
// if (_queuedRotationDelta.length2 > 0.0) {
// double deltaX = _queuedRotationDelta.x * rotationSensitivity;
// double deltaY = _queuedRotationDelta.y * rotationSensitivity;
// relativeRotation = Quaternion.axisAngle(current.up, -deltaX) *
// Quaternion.axisAngle(current.right, -deltaY);
// _queuedRotationDelta = Vector2.zero();
// }
// Apply (mouse) pan
if (_queuedTranslateDelta.length2 > 0.0) {
double deltaX = -_queuedTranslateDelta.x * panSensitivity;
double deltaY = _queuedTranslateDelta.y * panSensitivity;
double deltaZ = -_queuedTranslateDelta.z * panSensitivity;
// // Apply (mouse) pan
// if (_queuedTranslateDelta.length2 > 0.0) {
// double deltaX = -_queuedTranslateDelta.x * panSensitivity;
// double deltaY = _queuedTranslateDelta.y * panSensitivity;
// double deltaZ = -_queuedTranslateDelta.z * panSensitivity;
relativeTranslation += current.right * deltaX +
current.up * deltaY +
current.forward * deltaZ;
_queuedTranslateDelta = Vector3.zero();
}
// relativeTranslation += current.right * deltaX +
// current.up * deltaY +
// current.forward * deltaZ;
// _queuedTranslateDelta = Vector3.zero();
// }
// Apply zoom
if (_queuedZoomDelta != 0.0) {
var zoomTranslation = current.forward..scaled(zoomSensitivity);
zoomTranslation.scale(_queuedZoomDelta);
relativeTranslation += zoomTranslation;
_queuedZoomDelta = 0.0;
}
// // Apply zoom
// if (_queuedZoomDelta != 0.0) {
// print("_queuedZoomDelta $_queuedZoomDelta");
// var zoomTranslation = current.forward..scaled(zoomSensitivity);
// zoomTranslation.scale(_queuedZoomDelta);
// relativeTranslation += zoomTranslation;
// _queuedZoomDelta = 0.0;
// }
// Apply queued movement
if (_queuedMoveDelta.length2 > 0.0) {
relativeTranslation += (current.right * _queuedMoveDelta.x +
current.up * _queuedMoveDelta.y +
current.forward * _queuedMoveDelta.z) *
movementSensitivity;
// // Apply queued movement
// if (_queuedMoveDelta.length2 > 0.0) {
// relativeTranslation += (current.right * _queuedMoveDelta.x +
// current.up * _queuedMoveDelta.y +
// current.forward * _queuedMoveDelta.z) *
// movementSensitivity;
_queuedMoveDelta = Vector3.zero();
}
// _queuedMoveDelta = Vector3.zero();
// }
// // If the managed entity is not the active camera, we need to apply the rotation from the current camera model matrix
// // to the entity's translation
// if (await entity != activeCamera.getEntity()) {
// Matrix4 modelMatrix = await activeCamera.getModelMatrix();
// relativeTranslation = modelMatrix.getRotation() * relativeTranslation;
// }
// // // If the managed entity is not the active camera, we need to apply the rotation from the current camera model matrix
// // // to the entity's translation
// // if (await entity != activeCamera.getEntity()) {
// // Matrix4 modelMatrix = await activeCamera.getModelMatrix();
// // relativeTranslation = modelMatrix.getRotation() * relativeTranslation;
// // }
var updated = Matrix4.compose(
relativeTranslation, relativeRotation, Vector3(1, 1, 1)) *
current;
await activeCamera.setModelMatrix(updated);
// var updated = Matrix4.compose(
// relativeTranslation, relativeRotation, Vector3(1, 1, 1)) *
// current;
_executing = false;
return updated;
}
}
// await activeCamera.setModelMatrix(updated);
// _executing = false;
// return updated;
// }
// }

View File

@@ -0,0 +1,119 @@
import 'dart:async';
import 'package:vector_math/vector_math_64.dart';
import '../../../viewer/viewer.dart';
import '../../input.dart';
class FreeFlightInputHandlerDelegateV2 implements InputHandlerDelegate {
final View view;
final InputSensitivityOptions sensitivity;
FreeFlightInputHandlerDelegateV2(this.view,
{this.sensitivity = const InputSensitivityOptions()});
double? _scaleDelta;
@override
Future<void> handle(Set<InputEvent> events) async {
Vector2 rotation = Vector2.zero();
Vector3 translation = Vector3.zero();
final activeCamera = await view.getCamera();
Matrix4 current = await activeCamera.getModelMatrix();
for (final event in events) {
switch (event) {
case ScrollEvent(delta: final delta):
translation +=
Vector3(0, 0, sensitivity.scrollWheelSensitivity * delta);
case MouseEvent(
type: final type,
button: final button,
localPosition: final localPosition,
delta: final delta
):
switch (type) {
case MouseEventType.hover:
case MouseEventType.move:
rotation += delta.scaled(sensitivity.mouseSensitivity);
default:
break;
}
break;
case TouchEvent(type: final type, delta: final delta):
switch (type) {
// case TouchEventType.move:
// rotation += delta!;
case TouchEventType.tap:
case TouchEventType.doubleTap:
break;
}
break;
case ScaleStartEvent(numPointers: final numPointers):
_scaleDelta = 1;
break;
case ScaleUpdateEvent(
numPointers: final numPointers,
localFocalPoint: final localFocalPoint,
localFocalPointDelta: final localFocalPointDelta,
scale: final scale,
):
if (numPointers == 1) {
translation +=
Vector3(localFocalPointDelta!.$1 * sensitivity.touchSensitivity, localFocalPointDelta!.$2 * sensitivity.touchSensitivity, 0);
} else {
translation = Vector3(0,0, (_scaleDelta! - scale) * sensitivity.touchScaleSensitivity * current.getTranslation().length.abs() );
_scaleDelta = scale;
}
break;
case ScaleEndEvent(numPointers: final numPointers):
break;
case KeyEvent(type: final type, key: var key):
switch (key) {
case PhysicalKey.A:
translation += Vector3(
-sensitivity.keySensitivity,
0,
0,
);
break;
case PhysicalKey.S:
translation += Vector3(0, 0, sensitivity.keySensitivity);
break;
case PhysicalKey.D:
translation += Vector3(
sensitivity.keySensitivity,
0,
0,
);
break;
case PhysicalKey.W:
translation += Vector3(
0,
0,
-sensitivity.keySensitivity,
);
break;
}
break;
}
}
if (rotation.length2 + translation.length2 == 0.0) {
return;
}
var updated = current *
Matrix4.compose(
translation,
Quaternion.axisAngle(Vector3(0, 1, 0), rotation.x) *
Quaternion.axisAngle(Vector3(1, 0, 0), rotation.y),
Vector3.all(1));
await activeCamera.setModelMatrix(updated);
return updated;
}
}

View File

@@ -1,371 +1,371 @@
import 'dart:async';
import 'dart:math';
import 'package:thermion_dart/thermion_dart.dart';
class _Gizmo {
final ThermionViewer viewer;
final GizmoAsset _gizmo;
final transformUpdates = StreamController<({Matrix4 transform})>.broadcast();
Axis? _active;
final GizmoType type;
_Gizmo(this._gizmo, this.viewer, this.type);
static Future<_Gizmo> forType(ThermionViewer viewer, GizmoType type) async {
final view = await viewer.view;
return _Gizmo(await viewer.getGizmo(type), viewer, type);
}
Future dispose() async {
await transformUpdates.close();
await viewer.destroyAsset(_gizmo);
}
Future hide() async {
final scene = await viewer.view.getScene();
await scene.remove(_gizmo);
}
Future reveal() async {
final scene = await viewer.view.getScene();
await scene.add(_gizmo);
gizmoTransform = await _gizmo.getWorldTransform();
}
double _getAngleBetweenVectors(Vector2 v1, Vector2 v2) {
// Normalize vectors to ensure consistent rotation regardless of distance from center
v1.normalize();
v2.normalize();
// Calculate angle using atan2
double angle = atan2(v2.y, v2.x) - atan2(v1.y, v1.x);
// Ensure angle is between -π and π
if (angle > pi) angle -= 2 * pi;
if (angle < -pi) angle += 2 * pi;
return angle;
}
void checkHover(int x, int y) async {
_gizmo.pick(x, y, handler: (result, coords) async {
switch (result) {
case GizmoPickResultType.None:
await _gizmo.unhighlight();
_active = null;
break;
case GizmoPickResultType.AxisX:
_active = Axis.X;
case GizmoPickResultType.AxisY:
_active = Axis.Y;
case GizmoPickResultType.AxisZ:
_active = Axis.Z;
default:
}
});
}
Matrix4? gizmoTransform;
void _updateTransform(Vector2 currentPosition, Vector2 delta) async {
if (type == GizmoType.translation) {
await _updateTranslation(currentPosition, delta);
} else if (type == GizmoType.rotation) {
await _updateRotation(currentPosition, delta);
}
await _gizmo.setTransform(gizmoTransform!);
transformUpdates.add((transform: gizmoTransform!));
}
Future<void>? _updateTranslation(
Vector2 currentPosition, Vector2 delta) async {
var view = await viewer.view;
var camera = await viewer.getActiveCamera();
var viewport = await view.getViewport();
var projectionMatrix = await camera.getProjectionMatrix();
var viewMatrix = await camera.getViewMatrix();
var inverseViewMatrix = await camera.getModelMatrix();
var inverseProjectionMatrix = projectionMatrix.clone()..invert();
// get gizmo position in screenspace
var gizmoPositionWorldSpace = gizmoTransform!.getTranslation();
Vector4 gizmoClipSpace = projectionMatrix *
viewMatrix *
Vector4(gizmoPositionWorldSpace.x, gizmoPositionWorldSpace.y,
gizmoPositionWorldSpace.z, 1.0);
var gizmoNdc = gizmoClipSpace / gizmoClipSpace.w;
var gizmoScreenSpace = Vector2(((gizmoNdc.x / 2) + 0.5) * viewport.width,
viewport.height - (((gizmoNdc.y / 2) + 0.5) * viewport.height));
gizmoScreenSpace += delta;
gizmoNdc = Vector4(((gizmoScreenSpace.x / viewport.width) - 0.5) * 2,
(((gizmoScreenSpace.y / viewport.height)) - 0.5) * -2, gizmoNdc.z, 1.0);
var gizmoViewSpace = inverseProjectionMatrix * gizmoNdc;
gizmoViewSpace /= gizmoViewSpace.w;
var newPosition = (inverseViewMatrix * gizmoViewSpace).xyz;
Vector3 worldSpaceDelta = newPosition - gizmoTransform!.getTranslation();
worldSpaceDelta.multiply(_active!.asVector());
gizmoTransform!
.setTranslation(gizmoTransform!.getTranslation() + worldSpaceDelta);
}
Future<void>? _updateRotation(Vector2 currentPosition, Vector2 delta) async {
var camera = await viewer.view.getCamera();
var viewport = await viewer.view.getViewport();
var projectionMatrix = await camera.getProjectionMatrix();
var viewMatrix = await camera.getViewMatrix();
// Get gizmo center in screen space
var gizmoPositionWorldSpace = gizmoTransform!.getTranslation();
Vector4 gizmoClipSpace = projectionMatrix *
viewMatrix *
Vector4(gizmoPositionWorldSpace.x, gizmoPositionWorldSpace.y,
gizmoPositionWorldSpace.z, 1.0);
var gizmoNdc = gizmoClipSpace / gizmoClipSpace.w;
var gizmoScreenSpace = Vector2(((gizmoNdc.x / 2) + 0.5) * viewport.width,
viewport.height - (((gizmoNdc.y / 2) + 0.5) * viewport.height));
// Calculate vectors from gizmo center to previous and current mouse positions
var prevVector = (currentPosition - delta) - gizmoScreenSpace;
var currentVector = currentPosition - gizmoScreenSpace;
// Calculate rotation angle based on the active axis
double rotationAngle = 0.0;
switch (_active) {
case Axis.X:
// For X axis, project onto YZ plane
var prev = Vector2(prevVector.y, -prevVector.x);
var curr = Vector2(currentVector.y, -currentVector.x);
rotationAngle = _getAngleBetweenVectors(prev, curr);
break;
case Axis.Y:
// For Y axis, project onto XZ plane
var prev = Vector2(prevVector.x, -prevVector.y);
var curr = Vector2(currentVector.x, -currentVector.y);
rotationAngle = _getAngleBetweenVectors(prev, curr);
break;
case Axis.Z:
// For Z axis, use screen plane directly
rotationAngle = -1 * _getAngleBetweenVectors(prevVector, currentVector);
break;
default:
return;
}
// Create rotation matrix based on the active axis
var rotationMatrix = Matrix4.identity();
switch (_active) {
case Axis.X:
rotationMatrix.setRotationX(rotationAngle);
break;
case Axis.Y:
rotationMatrix.setRotationY(rotationAngle);
break;
case Axis.Z:
rotationMatrix.setRotationZ(rotationAngle);
break;
default:
return;
}
// Apply rotation to the current transform
gizmoTransform = gizmoTransform! * rotationMatrix;
}
}
class GizmoInputHandler extends InputHandler {
final ThermionViewer viewer;
late final _gizmos = <GizmoType, _Gizmo>{};
_Gizmo? _active;
ThermionEntity? _attached;
Future attach(ThermionEntity entity) async {
if (_attached != null) {
await detach();
}
_attached = entity;
if (_active != null) {
await FilamentApp.instance!.setParent(_attached!, _active!._gizmo.entity);
await _active!.reveal();
}
}
Future<Matrix4?> getGizmoTransform() async {
return _active?.gizmoTransform;
}
Future detach() async {
if (_attached == null) {
return;
}
await FilamentApp.instance!.setParent(_attached!, null);
await _active?.hide();
_attached = null;
}
final _initialized = Completer<bool>();
final _transformController = StreamController<Matrix4>.broadcast();
Stream<Matrix4> get transformUpdated => _transformController.stream;
final _pickResultController = StreamController<ThermionEntity?>.broadcast();
Stream<ThermionEntity?> get onPickResult => _pickResultController.stream;
GizmoInputHandler({required this.viewer, required GizmoType initialType}) {
initialize().then((_) {
setGizmoType(initialType);
});
}
GizmoType? getGizmoType() {
return _active?.type;
}
Future setGizmoType(GizmoType? type) async {
if (type == null) {
await detach();
_active?.hide();
_active = null;
} else {
_active?.hide();
_active = _gizmos[type]!;
_active!.reveal();
if (_attached != null) {
await attach(_attached!);
}
}
}
Future initialize() async {
if (_initialized.isCompleted) {
throw Exception("Already initialized");
}
await viewer.initialized;
_gizmos[GizmoType.translation] =
await _Gizmo.forType(viewer, GizmoType.translation);
_gizmos[GizmoType.rotation] =
await _Gizmo.forType(viewer, GizmoType.rotation);
await setGizmoType(GizmoType.translation);
for (final gizmo in _gizmos.values) {
gizmo.transformUpdates.stream.listen((update) {
_transformController.add(update.transform);
});
}
_initialized.complete(true);
}
@override
Future dispose() async {
_gizmos[GizmoType.rotation]!.dispose();
_gizmos[GizmoType.translation]!.dispose();
_gizmos.clear();
}
@override
InputAction? getActionForType(InputType gestureType) {
if (gestureType == InputType.LMB_DOWN) {
return InputAction.PICK;
}
throw UnimplementedError();
}
@override
Future<bool> get initialized => _initialized.future;
@override
void keyDown(PhysicalKey key) {}
@override
void keyUp(PhysicalKey key) {}
@override
Future<void>? onPointerDown(Vector2 localPosition, bool isMiddle) async {
if (!_initialized.isCompleted) {
return;
}
if (isMiddle) {
return;
}
await viewer.view.pick(localPosition.x.toInt(), localPosition.y.toInt(),
(result) async {
if (_active?._gizmo.isNonPickable(result.entity) == true ||
result.entity == FILAMENT_ENTITY_NULL) {
_pickResultController.add(null);
return;
}
if (_active?._gizmo.isGizmoEntity(result.entity) != true) {
_pickResultController.add(result.entity);
}
});
}
@override
Future<void>? onPointerHover(Vector2 localPosition, Vector2 delta) async {
if (!_initialized.isCompleted) {
return;
}
_active?.checkHover(localPosition.x.floor(), localPosition.y.floor());
}
@override
Future<void>? onPointerMove(
Vector2 localPosition, Vector2 delta, bool isMiddle) async {
if (!isMiddle && _active?._active != null) {
final scaledDelta = Vector2(
delta.x,
delta.y,
);
_active!._updateTransform(localPosition, scaledDelta);
return;
}
}
@override
Future<void>? onPointerScroll(
Vector2 localPosition, double scrollDelta) async {}
@override
Future<void>? onPointerUp(bool isMiddle) async {}
@override
Future<void>? onScaleEnd(int pointerCount, double velocity) {}
@override
Future<void>? onScaleStart(
Vector2 focalPoint, int pointerCount, Duration? sourceTimestamp) {}
@override
Future<void>? onScaleUpdate(
Vector2 focalPoint,
Vector2 focalPointDelta,
double horizontalScale,
double verticalScale,
double scale,
int pointerCount,
double rotation,
Duration? sourceTimestamp) {}
@override
void setActionForType(InputType gestureType, InputAction gestureAction) {
throw UnimplementedError();
}
}
// import 'dart:async';
// import 'dart:math';
// import 'package:thermion_dart/thermion_dart.dart';
// class _Gizmo {
// final ThermionViewer viewer;
// final GizmoAsset _gizmo;
// final transformUpdates = StreamController<({Matrix4 transform})>.broadcast();
// Axis? _active;
// final GizmoType type;
// _Gizmo(this._gizmo, this.viewer, this.type);
// static Future<_Gizmo> forType(ThermionViewer viewer, GizmoType type) async {
// final view = await viewer.view;
// return _Gizmo(await viewer.getGizmo(type), viewer, type);
// }
// Future dispose() async {
// await transformUpdates.close();
// await viewer.destroyAsset(_gizmo);
// }
// Future hide() async {
// final scene = await viewer.view.getScene();
// await scene.remove(_gizmo);
// }
// Future reveal() async {
// final scene = await viewer.view.getScene();
// await scene.add(_gizmo);
// gizmoTransform = await _gizmo.getWorldTransform();
// }
// double _getAngleBetweenVectors(Vector2 v1, Vector2 v2) {
// // Normalize vectors to ensure consistent rotation regardless of distance from center
// v1.normalize();
// v2.normalize();
// // Calculate angle using atan2
// double angle = atan2(v2.y, v2.x) - atan2(v1.y, v1.x);
// // Ensure angle is between -π and π
// if (angle > pi) angle -= 2 * pi;
// if (angle < -pi) angle += 2 * pi;
// return angle;
// }
// void checkHover(int x, int y) async {
// _gizmo.pick(x, y, handler: (result, coords) async {
// switch (result) {
// case GizmoPickResultType.None:
// await _gizmo.unhighlight();
// _active = null;
// break;
// case GizmoPickResultType.AxisX:
// _active = Axis.X;
// case GizmoPickResultType.AxisY:
// _active = Axis.Y;
// case GizmoPickResultType.AxisZ:
// _active = Axis.Z;
// default:
// }
// });
// }
// Matrix4? gizmoTransform;
// void _updateTransform(Vector2 currentPosition, Vector2 delta) async {
// if (type == GizmoType.translation) {
// await _updateTranslation(currentPosition, delta);
// } else if (type == GizmoType.rotation) {
// await _updateRotation(currentPosition, delta);
// }
// await _gizmo.setTransform(gizmoTransform!);
// transformUpdates.add((transform: gizmoTransform!));
// }
// Future<void>? _updateTranslation(
// Vector2 currentPosition, Vector2 delta) async {
// var view = await viewer.view;
// var camera = await viewer.getActiveCamera();
// var viewport = await view.getViewport();
// var projectionMatrix = await camera.getProjectionMatrix();
// var viewMatrix = await camera.getViewMatrix();
// var inverseViewMatrix = await camera.getModelMatrix();
// var inverseProjectionMatrix = projectionMatrix.clone()..invert();
// // get gizmo position in screenspace
// var gizmoPositionWorldSpace = gizmoTransform!.getTranslation();
// Vector4 gizmoClipSpace = projectionMatrix *
// viewMatrix *
// Vector4(gizmoPositionWorldSpace.x, gizmoPositionWorldSpace.y,
// gizmoPositionWorldSpace.z, 1.0);
// var gizmoNdc = gizmoClipSpace / gizmoClipSpace.w;
// var gizmoScreenSpace = Vector2(((gizmoNdc.x / 2) + 0.5) * viewport.width,
// viewport.height - (((gizmoNdc.y / 2) + 0.5) * viewport.height));
// gizmoScreenSpace += delta;
// gizmoNdc = Vector4(((gizmoScreenSpace.x / viewport.width) - 0.5) * 2,
// (((gizmoScreenSpace.y / viewport.height)) - 0.5) * -2, gizmoNdc.z, 1.0);
// var gizmoViewSpace = inverseProjectionMatrix * gizmoNdc;
// gizmoViewSpace /= gizmoViewSpace.w;
// var newPosition = (inverseViewMatrix * gizmoViewSpace).xyz;
// Vector3 worldSpaceDelta = newPosition - gizmoTransform!.getTranslation();
// worldSpaceDelta.multiply(_active!.asVector());
// gizmoTransform!
// .setTranslation(gizmoTransform!.getTranslation() + worldSpaceDelta);
// }
// Future<void>? _updateRotation(Vector2 currentPosition, Vector2 delta) async {
// var camera = await viewer.view.getCamera();
// var viewport = await viewer.view.getViewport();
// var projectionMatrix = await camera.getProjectionMatrix();
// var viewMatrix = await camera.getViewMatrix();
// // Get gizmo center in screen space
// var gizmoPositionWorldSpace = gizmoTransform!.getTranslation();
// Vector4 gizmoClipSpace = projectionMatrix *
// viewMatrix *
// Vector4(gizmoPositionWorldSpace.x, gizmoPositionWorldSpace.y,
// gizmoPositionWorldSpace.z, 1.0);
// var gizmoNdc = gizmoClipSpace / gizmoClipSpace.w;
// var gizmoScreenSpace = Vector2(((gizmoNdc.x / 2) + 0.5) * viewport.width,
// viewport.height - (((gizmoNdc.y / 2) + 0.5) * viewport.height));
// // Calculate vectors from gizmo center to previous and current mouse positions
// var prevVector = (currentPosition - delta) - gizmoScreenSpace;
// var currentVector = currentPosition - gizmoScreenSpace;
// // Calculate rotation angle based on the active axis
// double rotationAngle = 0.0;
// switch (_active) {
// case Axis.X:
// // For X axis, project onto YZ plane
// var prev = Vector2(prevVector.y, -prevVector.x);
// var curr = Vector2(currentVector.y, -currentVector.x);
// rotationAngle = _getAngleBetweenVectors(prev, curr);
// break;
// case Axis.Y:
// // For Y axis, project onto XZ plane
// var prev = Vector2(prevVector.x, -prevVector.y);
// var curr = Vector2(currentVector.x, -currentVector.y);
// rotationAngle = _getAngleBetweenVectors(prev, curr);
// break;
// case Axis.Z:
// // For Z axis, use screen plane directly
// rotationAngle = -1 * _getAngleBetweenVectors(prevVector, currentVector);
// break;
// default:
// return;
// }
// // Create rotation matrix based on the active axis
// var rotationMatrix = Matrix4.identity();
// switch (_active) {
// case Axis.X:
// rotationMatrix.setRotationX(rotationAngle);
// break;
// case Axis.Y:
// rotationMatrix.setRotationY(rotationAngle);
// break;
// case Axis.Z:
// rotationMatrix.setRotationZ(rotationAngle);
// break;
// default:
// return;
// }
// // Apply rotation to the current transform
// gizmoTransform = gizmoTransform! * rotationMatrix;
// }
// }
// class GizmoInputHandler extends InputHandler {
// final ThermionViewer viewer;
// late final _gizmos = <GizmoType, _Gizmo>{};
// _Gizmo? _active;
// ThermionEntity? _attached;
// Future attach(ThermionEntity entity) async {
// if (_attached != null) {
// await detach();
// }
// _attached = entity;
// if (_active != null) {
// await FilamentApp.instance!.setParent(_attached!, _active!._gizmo.entity);
// await _active!.reveal();
// }
// }
// Future<Matrix4?> getGizmoTransform() async {
// return _active?.gizmoTransform;
// }
// Future detach() async {
// if (_attached == null) {
// return;
// }
// await FilamentApp.instance!.setParent(_attached!, null);
// await _active?.hide();
// _attached = null;
// }
// final _initialized = Completer<bool>();
// final _transformController = StreamController<Matrix4>.broadcast();
// Stream<Matrix4> get transformUpdated => _transformController.stream;
// final _pickResultController = StreamController<ThermionEntity?>.broadcast();
// Stream<ThermionEntity?> get onPickResult => _pickResultController.stream;
// GizmoInputHandler({required this.viewer, required GizmoType initialType}) {
// initialize().then((_) {
// setGizmoType(initialType);
// });
// }
// GizmoType? getGizmoType() {
// return _active?.type;
// }
// Future setGizmoType(GizmoType? type) async {
// if (type == null) {
// await detach();
// _active?.hide();
// _active = null;
// } else {
// _active?.hide();
// _active = _gizmos[type]!;
// _active!.reveal();
// if (_attached != null) {
// await attach(_attached!);
// }
// }
// }
// Future initialize() async {
// if (_initialized.isCompleted) {
// throw Exception("Already initialized");
// }
// await viewer.initialized;
// _gizmos[GizmoType.translation] =
// await _Gizmo.forType(viewer, GizmoType.translation);
// _gizmos[GizmoType.rotation] =
// await _Gizmo.forType(viewer, GizmoType.rotation);
// await setGizmoType(GizmoType.translation);
// for (final gizmo in _gizmos.values) {
// gizmo.transformUpdates.stream.listen((update) {
// _transformController.add(update.transform);
// });
// }
// _initialized.complete(true);
// }
// @override
// Future dispose() async {
// _gizmos[GizmoType.rotation]!.dispose();
// _gizmos[GizmoType.translation]!.dispose();
// _gizmos.clear();
// }
// @override
// InputAction? getActionForType(InputType gestureType) {
// if (gestureType == InputType.LMB_DOWN) {
// return InputAction.PICK;
// }
// throw UnimplementedError();
// }
// @override
// Future<bool> get initialized => _initialized.future;
// @override
// void keyDown(PhysicalKey key) {}
// @override
// void keyUp(PhysicalKey key) {}
// @override
// Future<void>? onPointerDown(Vector2 localPosition, bool isMiddle) async {
// if (!_initialized.isCompleted) {
// return;
// }
// if (isMiddle) {
// return;
// }
// await viewer.view.pick(localPosition.x.toInt(), localPosition.y.toInt(),
// (result) async {
// if (_active?._gizmo.isNonPickable(result.entity) == true ||
// result.entity == FILAMENT_ENTITY_NULL) {
// _pickResultController.add(null);
// return;
// }
// if (_active?._gizmo.isGizmoEntity(result.entity) != true) {
// _pickResultController.add(result.entity);
// }
// });
// }
// @override
// Future<void>? onPointerHover(Vector2 localPosition, Vector2 delta) async {
// if (!_initialized.isCompleted) {
// return;
// }
// _active?.checkHover(localPosition.x.floor(), localPosition.y.floor());
// }
// @override
// Future<void>? onPointerMove(
// Vector2 localPosition, Vector2 delta, bool isMiddle) async {
// if (!isMiddle && _active?._active != null) {
// final scaledDelta = Vector2(
// delta.x,
// delta.y,
// );
// _active!._updateTransform(localPosition, scaledDelta);
// return;
// }
// }
// @override
// Future<void>? onPointerScroll(
// Vector2 localPosition, double scrollDelta) async {}
// @override
// Future<void>? onPointerUp(bool isMiddle) async {}
// @override
// Future<void>? onScaleEnd(int pointerCount, double velocity) {}
// @override
// Future<void>? onScaleStart(
// Vector2 focalPoint, int pointerCount, Duration? sourceTimestamp) {}
// @override
// Future<void>? onScaleUpdate(
// Vector2 focalPoint,
// Vector2 focalPointDelta,
// double horizontalScale,
// double verticalScale,
// double scale,
// int pointerCount,
// double rotation,
// Duration? sourceTimestamp) {}
// @override
// void setActionForType(InputType gestureType, InputAction gestureAction) {
// throw UnimplementedError();
// }
// }

View File

@@ -1,121 +1,120 @@
import 'dart:async';
import 'dart:math';
import 'package:vector_math/vector_math_64.dart';
import '../../../viewer/viewer.dart';
import '../delegates.dart';
import '../input_handler.dart';
// import 'dart:async';
// import 'dart:math';
// import 'package:vector_math/vector_math_64.dart';
// import '../../../viewer/viewer.dart';
// import '../../input.dart';
class OverTheShoulderCameraDelegate implements InputHandlerDelegate {
final ThermionViewer viewer;
// class OverTheShoulderCameraDelegate implements InputHandlerDelegate {
// final ThermionViewer viewer;
late ThermionAsset player;
late Camera camera;
// late ThermionAsset player;
// late Camera camera;
final double rotationSensitivity;
final double movementSensitivity;
final double zoomSensitivity;
final double panSensitivity;
final double? clampY;
// final double rotationSensitivity;
// final double movementSensitivity;
// final double zoomSensitivity;
// final double panSensitivity;
// final double? clampY;
static final _up = Vector3(0, 1, 0);
static final _forward = Vector3(0, 0, -1);
static final Vector3 _right = Vector3(1, 0, 0);
// 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();
double _queuedZoomDelta = 0.0;
Vector3 _queuedMoveDelta = Vector3.zero();
// Vector2 _queuedRotationDelta = Vector2.zero();
// double _queuedZoomDelta = 0.0;
// Vector3 _queuedMoveDelta = Vector3.zero();
final cameraPosition = Vector3(-0.5, 2.5, -3);
final cameraUp = Vector3(0, 1, 0);
var cameraLookAt = Vector3(0, 0.5, 3);
// final cameraPosition = Vector3(-0.5, 2.5, -3);
// final cameraUp = Vector3(0, 1, 0);
// var cameraLookAt = Vector3(0, 0.5, 3);
final void Function(Matrix4 transform)? onUpdate;
// final void Function(Matrix4 transform)? onUpdate;
OverTheShoulderCameraDelegate(this.viewer, this.player, this.camera,
{this.rotationSensitivity = 0.001,
this.movementSensitivity = 0.1,
this.zoomSensitivity = 0.1,
this.panSensitivity = 0.1,
this.clampY,
ThermionEntity? entity,
this.onUpdate}) {}
// OverTheShoulderCameraDelegate(this.viewer, this.player, this.camera,
// {this.rotationSensitivity = 0.001,
// this.movementSensitivity = 0.1,
// this.zoomSensitivity = 0.1,
// this.panSensitivity = 0.1,
// this.clampY,
// ThermionEntity? entity,
// this.onUpdate}) {}
@override
Future<void> queue(InputAction action, Vector3? delta) async {
if (delta == null) return;
// @override
// Future<void> 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:
_queuedMoveDelta += delta;
break;
case InputAction.PICK:
_queuedZoomDelta += delta.z;
break;
case InputAction.NONE:
break;
case InputAction.ZOOM:
break;
}
}
// switch (action) {
// case InputAction.ROTATE:
// _queuedRotationDelta += Vector2(delta.x, delta.y);
// break;
// case InputAction.TRANSLATE:
// _queuedMoveDelta += delta;
// break;
// case InputAction.PICK:
// _queuedZoomDelta += delta.z;
// break;
// case InputAction.NONE:
// break;
// case InputAction.ZOOM:
// break;
// }
// }
static bool _executing = false;
static bool get executing => _executing;
// static bool _executing = false;
// static bool get executing => _executing;
@override
Future<Matrix4?> execute() async {
if (_executing) {
return null;
}
// @override
// Future<Matrix4?> execute() async {
// if (_executing) {
// return null;
// }
_executing = true;
// _executing = true;
if (_queuedRotationDelta.length2 == 0.0 &&
_queuedZoomDelta == 0.0 &&
_queuedMoveDelta.length2 == 0.0) {
_executing = false;
return null;
}
// if (_queuedRotationDelta.length2 == 0.0 &&
// _queuedZoomDelta == 0.0 &&
// _queuedMoveDelta.length2 == 0.0) {
// _executing = false;
// return null;
// }
Matrix4 currentPlayerTransform = await player.getWorldTransform();
// Matrix4 currentPlayerTransform = await player.getWorldTransform();
// first we need to convert the move vector to player space
var newTransform =
Matrix4.translation(_queuedMoveDelta * movementSensitivity);
// // first we need to convert the move vector to player space
// var newTransform =
// Matrix4.translation(_queuedMoveDelta * movementSensitivity);
_queuedMoveDelta = Vector3.zero();
Matrix4 newPlayerTransform = newTransform * currentPlayerTransform;
await player.setTransform(newPlayerTransform);
// _queuedMoveDelta = Vector3.zero();
// Matrix4 newPlayerTransform = newTransform * currentPlayerTransform;
// await player.setTransform(newPlayerTransform);
if (_queuedZoomDelta != 0.0) {
// Ignore zoom
}
// if (_queuedZoomDelta != 0.0) {
// // Ignore zoom
// }
var inverted = newPlayerTransform.clone()..invert();
// var inverted = newPlayerTransform.clone()..invert();
// camera is always looking at -Z, whereas models generally face towards +Z
if (_queuedRotationDelta.length2 > 0.0) {
double deltaX = _queuedRotationDelta.x * rotationSensitivity;
double deltaY = _queuedRotationDelta.y * rotationSensitivity;
// // camera is always looking at -Z, whereas models generally face towards +Z
// if (_queuedRotationDelta.length2 > 0.0) {
// double deltaX = _queuedRotationDelta.x * rotationSensitivity;
// double deltaY = _queuedRotationDelta.y * rotationSensitivity;
cameraLookAt = Matrix4.rotationY(-deltaX) *
Matrix4.rotationX(-deltaY) *
cameraLookAt;
_queuedRotationDelta = Vector2.zero();
}
// cameraLookAt = Matrix4.rotationY(-deltaX) *
// Matrix4.rotationX(-deltaY) *
// cameraLookAt;
// _queuedRotationDelta = Vector2.zero();
// }
var newCameraViewMatrix =
makeViewMatrix(cameraPosition, cameraLookAt, cameraUp);
newCameraViewMatrix.invert();
var newCameraTransform = newPlayerTransform * newCameraViewMatrix;
await camera.setTransform(newCameraTransform);
// var newCameraViewMatrix =
// makeViewMatrix(cameraPosition, cameraLookAt, cameraUp);
// newCameraViewMatrix.invert();
// var newCameraTransform = newPlayerTransform * newCameraViewMatrix;
// await camera.setTransform(newCameraTransform);
// await viewer.queueTransformUpdates(
// [camera.getEntity(), player], [newCameraTransform, newPlayerTransform]);
onUpdate?.call(newPlayerTransform);
_executing = false;
return newCameraTransform;
}
}
// // await viewer.queueTransformUpdates(
// // [camera.getEntity(), player], [newCameraTransform, newPlayerTransform]);
// onUpdate?.call(newPlayerTransform);
// _executing = false;
// return newCameraTransform;
// }
// }

View File

@@ -1,64 +1,32 @@
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, // two fingers pinchin in/out
SCALE2_ROTATE, // two fingers rotating in a circle
SCALE2_MOVE, // two fingers sliding along a line
SCROLLWHEEL,
POINTER_MOVE,
KEYDOWN_W,
KEYDOWN_A,
KEYDOWN_S,
KEYDOWN_D,
}
enum PhysicalKey { W, A, S, D }
enum InputAction { TRANSLATE, ROTATE, PICK, ZOOM, NONE }
import 'input_types.dart';
///
/// An interface for handling user device input events.
///
abstract class InputHandler {
@Deprecated("Use @transformUpdated instead")
Stream get cameraUpdated => transformUpdated;
///
///
///
Future? handle(InputEvent event);
Stream<Matrix4> get transformUpdated;
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(
Vector2 focalPoint, int pointerCount, Duration? sourceTimestamp);
Future? onScaleUpdate(
Vector2 focalPoint,
Vector2 focalPointDelta,
double horizontalScale,
double verticalScale,
double scale,
int pointerCount,
double rotation,
Duration? sourceTimestamp);
Future? onScaleEnd(int pointerCount, double velocity);
Future<bool> get initialized;
///
///
///
Future dispose();
void setActionForType(InputType gestureType, InputAction gestureAction);
InputAction? getActionForType(InputType gestureType);
void keyDown(PhysicalKey key);
void keyUp(PhysicalKey key);
}
class InputSensitivityOptions {
final double touchSensitivity;
final double touchScaleSensitivity;
final double mouseSensitivity;
final double keySensitivity;
final double scrollWheelSensitivity;
const InputSensitivityOptions(
{this.touchSensitivity = 0.001,
this.touchScaleSensitivity = 2.0,
this.mouseSensitivity = 0.001,
this.scrollWheelSensitivity = 0.01,
this.keySensitivity = 0.1});
}

View File

@@ -0,0 +1,92 @@
import 'package:vector_math/vector_math_64.dart';
sealed class InputEvent {}
enum MouseButton { left, middle, right }
enum MouseEventType { hover, move, buttonDown, buttonUp }
class MouseEvent extends InputEvent {
final MouseEventType type;
final MouseButton? button;
final Vector2 localPosition;
final Vector2 delta;
MouseEvent(this.type, this.button, this.localPosition, this.delta);
}
enum TouchEventType {
// move,
tap,
doubleTap,
}
class TouchEvent extends InputEvent {
final TouchEventType type;
final Vector2? localPosition;
final Vector2? delta;
TouchEvent(this.type, this.localPosition, this.delta);
}
enum ScaleEventType { start, update, end }
class ScaleStartEvent extends InputEvent {
final int numPointers;
final ScaleEventType type = ScaleEventType.start;
final (double, double) localFocalPoint;
ScaleStartEvent({
required this.numPointers,
required this.localFocalPoint,
});
}
class ScaleEndEvent extends InputEvent {
final int numPointers;
final ScaleEventType type = ScaleEventType.end;
ScaleEndEvent({
required this.numPointers,
});
}
class ScaleUpdateEvent extends InputEvent {
final int numPointers;
final ScaleEventType type = ScaleEventType.update;
final (double, double) localFocalPoint;
final (double, double)? localFocalPointDelta;
final double rotation;
final double scale;
final double horizontalScale;
final double verticalScale;
ScaleUpdateEvent(
{required this.numPointers,
required this.localFocalPoint,
required this.localFocalPointDelta,
required this.rotation,
required this.scale,
required this.horizontalScale,
required this.verticalScale});
}
class ScrollEvent extends InputEvent {
final Vector2 localPosition;
final double delta;
ScrollEvent({required this.localPosition, required this.delta});
}
class KeyEvent extends InputEvent {
final KeyEventType type;
final PhysicalKey key;
KeyEvent(this.type, this.key);
}
enum KeyEventType { down, up }
enum PhysicalKey { W, A, S, D }
enum InputAction { TRANSLATE, ROTATE, PICK, ZOOM, NONE }