refactor: gizmo/input handler improvements
This commit is contained in:
@@ -12,8 +12,8 @@ class DelegateInputHandler implements InputHandler {
|
||||
Stream<List<InputType>> get gestures => _gesturesController.stream;
|
||||
final _gesturesController = StreamController<List<InputType>>.broadcast();
|
||||
|
||||
Stream get cameraUpdated => _cameraUpdatedController.stream;
|
||||
final _cameraUpdatedController = StreamController.broadcast();
|
||||
Stream<Matrix4> get cameraUpdated => _cameraUpdatedController.stream;
|
||||
final _cameraUpdatedController = StreamController<Matrix4>.broadcast();
|
||||
|
||||
final _logger = Logger("DelegateInputHandler");
|
||||
|
||||
@@ -172,11 +172,13 @@ class DelegateInputHandler implements InputHandler {
|
||||
}
|
||||
}
|
||||
|
||||
await transformDelegate?.execute();
|
||||
var transform = await transformDelegate?.execute();
|
||||
var updates = _inputDeltas.keys.followedBy(keyTypes).toList();
|
||||
if (updates.isNotEmpty) {
|
||||
_gesturesController.add(updates);
|
||||
_cameraUpdatedController.add(true);
|
||||
}
|
||||
if (transform != null) {
|
||||
_cameraUpdatedController.add(transform);
|
||||
}
|
||||
|
||||
_inputDeltas.clear();
|
||||
@@ -310,4 +312,7 @@ class DelegateInputHandler implements InputHandler {
|
||||
throw UnimplementedError("Only pointerCount <= 2 supported");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<Matrix4> get transformUpdated => cameraUpdated;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'input_handler.dart';
|
||||
|
||||
abstract class InputHandlerDelegate {
|
||||
Future queue(InputAction action, Vector3? delta);
|
||||
Future execute();
|
||||
Future<Matrix4?> execute();
|
||||
}
|
||||
|
||||
abstract class VelocityDelegate {
|
||||
|
||||
@@ -14,7 +14,7 @@ class FixedOrbitRotateInputHandlerDelegate implements InputHandlerDelegate {
|
||||
late Future<Camera> _camera;
|
||||
final double minimumDistance;
|
||||
late final Vector3 target;
|
||||
|
||||
|
||||
final double rotationSensitivity;
|
||||
final double zoomSensitivity;
|
||||
|
||||
@@ -70,13 +70,13 @@ class FixedOrbitRotateInputHandlerDelegate implements InputHandlerDelegate {
|
||||
bool _executing = false;
|
||||
|
||||
@override
|
||||
Future<void> execute() async {
|
||||
Future<Matrix4?> execute() async {
|
||||
if (_queuedRotationDelta.length2 == 0.0 && _queuedZoomDelta == 0.0) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_executing) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
_executing = true;
|
||||
@@ -97,6 +97,8 @@ class FixedOrbitRotateInputHandlerDelegate implements InputHandlerDelegate {
|
||||
currentPosition = Vector3(0, 0, minimumDistance);
|
||||
}
|
||||
|
||||
Matrix4? updatedModelMatrix = null;
|
||||
|
||||
// Zoom
|
||||
if (_queuedZoomDelta != 0.0) {
|
||||
var newPosition = currentPosition +
|
||||
@@ -116,6 +118,7 @@ class FixedOrbitRotateInputHandlerDelegate implements InputHandlerDelegate {
|
||||
newViewMatrix.invert();
|
||||
|
||||
await (await _camera).setModelMatrix(newViewMatrix);
|
||||
updatedModelMatrix = newViewMatrix;
|
||||
}
|
||||
} else if (_queuedRotationDelta.length != 0) {
|
||||
double rotateX = _queuedRotationDelta.x * rotationSensitivity;
|
||||
@@ -134,6 +137,7 @@ class FixedOrbitRotateInputHandlerDelegate implements InputHandlerDelegate {
|
||||
|
||||
modelMatrix = rot1 * rot2 * modelMatrix;
|
||||
await (await _camera).setModelMatrix(modelMatrix);
|
||||
updatedModelMatrix = modelMatrix;
|
||||
}
|
||||
|
||||
// Reset queued deltas
|
||||
@@ -141,5 +145,6 @@ class FixedOrbitRotateInputHandlerDelegate implements InputHandlerDelegate {
|
||||
_queuedZoomDelta = 0.0;
|
||||
|
||||
_executing = false;
|
||||
return updatedModelMatrix;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,9 +61,9 @@ class FreeFlightInputHandlerDelegate implements InputHandlerDelegate {
|
||||
bool _executing = false;
|
||||
|
||||
@override
|
||||
Future<void> execute() async {
|
||||
Future<Matrix4?> execute() async {
|
||||
if (_executing) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
_executing = true;
|
||||
@@ -73,7 +73,7 @@ class FreeFlightInputHandlerDelegate implements InputHandlerDelegate {
|
||||
_queuedZoomDelta == 0.0 &&
|
||||
_queuedMoveDelta.length2 == 0.0) {
|
||||
_executing = false;
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
final activeCamera = await viewer.getActiveCamera();
|
||||
@@ -128,12 +128,12 @@ class FreeFlightInputHandlerDelegate implements InputHandlerDelegate {
|
||||
relativeTranslation = modelMatrix.getRotation() * relativeTranslation;
|
||||
}
|
||||
|
||||
await viewer.setTransform(
|
||||
await entity,
|
||||
Matrix4.compose(
|
||||
relativeTranslation, relativeRotation, Vector3(1, 1, 1)) *
|
||||
current);
|
||||
var updated = Matrix4.compose(
|
||||
relativeTranslation, relativeRotation, Vector3(1, 1, 1)) *
|
||||
current;
|
||||
await viewer.setTransform(await entity, updated);
|
||||
|
||||
_executing = false;
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,27 +4,35 @@ import 'package:thermion_dart/thermion_dart.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
|
||||
class _Gizmo {
|
||||
bool isVisible = false;
|
||||
final ThermionViewer viewer;
|
||||
|
||||
final GizmoAsset _gizmo;
|
||||
ThermionEntity? _attachedTo;
|
||||
|
||||
final attachedTo = StreamController<ThermionEntity?>.broadcast();
|
||||
final transformUpdates = StreamController<({Matrix4 transform})>.broadcast();
|
||||
|
||||
final GizmoType _gizmoType;
|
||||
Axis? _active;
|
||||
final GizmoType type;
|
||||
|
||||
_Gizmo(this._gizmo, this.viewer, this._gizmoType);
|
||||
_Gizmo(this._gizmo, this.viewer, this.type);
|
||||
|
||||
static Future<_Gizmo> forType(ThermionViewer viewer, GizmoType type) async {
|
||||
final view = await viewer.getViewAt(0);
|
||||
return _Gizmo(await viewer.createGizmo(view, type), viewer, type);
|
||||
}
|
||||
|
||||
final _onEntityTransformUpdated = StreamController<
|
||||
({ThermionEntity entity, Matrix4 transform})>.broadcast();
|
||||
Future dispose() async {
|
||||
await transformUpdates.close();
|
||||
await viewer.removeAsset(_gizmo);
|
||||
}
|
||||
|
||||
Axis? _active;
|
||||
Axis? get active => _active;
|
||||
Future hide() async {
|
||||
await _gizmo.removeFromScene();
|
||||
}
|
||||
|
||||
Future reveal() async {
|
||||
await _gizmo.addToScene();
|
||||
gizmoTransform = await viewer.getWorldTransform(_gizmo.entity);
|
||||
}
|
||||
|
||||
double _getAngleBetweenVectors(Vector2 v1, Vector2 v2) {
|
||||
// Normalize vectors to ensure consistent rotation regardless of distance from center
|
||||
@@ -61,49 +69,16 @@ class _Gizmo {
|
||||
|
||||
Matrix4? gizmoTransform;
|
||||
|
||||
Future attach(ThermionEntity entity) async {
|
||||
print("Attached to ${entity}");
|
||||
|
||||
if (_attachedTo != null && entity != _attachedTo) {
|
||||
await viewer.setParent(_attachedTo!, 0);
|
||||
}
|
||||
|
||||
_attachedTo = entity;
|
||||
attachedTo.add(_attachedTo);
|
||||
|
||||
await viewer.setParent(_gizmo.entity, entity);
|
||||
await viewer.setTransform(_gizmo.entity, Matrix4.identity());
|
||||
|
||||
if (!isVisible) {
|
||||
await _gizmo.addToScene();
|
||||
isVisible = true;
|
||||
}
|
||||
gizmoTransform = await viewer.getWorldTransform(entity);
|
||||
}
|
||||
|
||||
Future detach() async {
|
||||
await _gizmo.removeFromScene();
|
||||
if (_attachedTo != null) {
|
||||
await viewer.setParent(_attachedTo!, 0);
|
||||
}
|
||||
attachedTo.add(null);
|
||||
_active = null;
|
||||
isVisible = false;
|
||||
}
|
||||
|
||||
void _updateTransform(Vector2 currentPosition, Vector2 delta) async {
|
||||
if (_attachedTo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_gizmoType == GizmoType.translation) {
|
||||
if (type == GizmoType.translation) {
|
||||
await _updateTranslation(currentPosition, delta);
|
||||
} else if (_gizmoType == GizmoType.rotation) {
|
||||
} else if (type == GizmoType.rotation) {
|
||||
await _updateRotation(currentPosition, delta);
|
||||
}
|
||||
|
||||
_onEntityTransformUpdated
|
||||
.add((entity: _attachedTo!, transform: gizmoTransform!));
|
||||
await viewer.setTransform(_gizmo.entity, gizmoTransform!);
|
||||
|
||||
transformUpdates.add((transform: gizmoTransform!));
|
||||
}
|
||||
|
||||
Future<void> _updateTranslation(
|
||||
@@ -143,8 +118,6 @@ class _Gizmo {
|
||||
|
||||
gizmoTransform!
|
||||
.setTranslation(gizmoTransform!.getTranslation() + worldSpaceDelta);
|
||||
|
||||
await viewer.setTransform(_attachedTo!, gizmoTransform!);
|
||||
}
|
||||
|
||||
Future<void> _updateRotation(Vector2 currentPosition, Vector2 delta) async {
|
||||
@@ -210,7 +183,6 @@ class _Gizmo {
|
||||
|
||||
// Apply rotation to the current transform
|
||||
gizmoTransform = gizmoTransform! * rotationMatrix;
|
||||
await viewer.setTransform(_attachedTo!, gizmoTransform!);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,56 +193,60 @@ class GizmoInputHandler extends InputHandler {
|
||||
|
||||
late final _gizmos = <GizmoType, _Gizmo>{};
|
||||
|
||||
_Gizmo? active;
|
||||
_Gizmo? _active;
|
||||
|
||||
StreamSubscription? _entityTransformUpdatedListener;
|
||||
StreamSubscription? _attachedToListener;
|
||||
ThermionEntity? _attached;
|
||||
|
||||
final _attachedTo = StreamController<ThermionEntity?>.broadcast();
|
||||
Stream<ThermionEntity?> get attachedTo => _attachedTo.stream;
|
||||
|
||||
GizmoType? getGizmoType() {
|
||||
return active?._gizmoType;
|
||||
Future attach(ThermionEntity entity) async {
|
||||
if (_attached != null) {
|
||||
await detach();
|
||||
}
|
||||
_attached = entity;
|
||||
if (_active != null) {
|
||||
await viewer.setParent(_attached!, _active!._gizmo.entity);
|
||||
await _active!.reveal();
|
||||
}
|
||||
}
|
||||
|
||||
Future setGizmoType(GizmoType? type) async {
|
||||
if (type == null) {
|
||||
await active?.detach();
|
||||
active = null;
|
||||
Future detach() async {
|
||||
if (_attached == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var target = _gizmos[type]!;
|
||||
if (target != active) {
|
||||
await _entityTransformUpdatedListener?.cancel();
|
||||
await _attachedToListener?.cancel();
|
||||
if (active?._attachedTo != null) {
|
||||
var attachedTo = active!._attachedTo!;
|
||||
await active!.detach();
|
||||
await target.attach(attachedTo);
|
||||
}
|
||||
active = target;
|
||||
_entityTransformUpdatedListener =
|
||||
active!._onEntityTransformUpdated.stream.listen((event) {
|
||||
_transformUpdatedController.add(event);
|
||||
});
|
||||
|
||||
_attachedToListener = active!.attachedTo.stream.listen((entity) {
|
||||
_attachedTo.add(entity);
|
||||
});
|
||||
}
|
||||
await viewer.setParent(_attached!, 0);
|
||||
await _active?.hide();
|
||||
_attached = null;
|
||||
}
|
||||
|
||||
final _transformUpdatedController =
|
||||
StreamController<({ThermionEntity entity, Matrix4 transform})>();
|
||||
Stream<({ThermionEntity entity, Matrix4 transform})>
|
||||
get onEntityTransformUpdated => _transformUpdatedController.stream;
|
||||
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.wrapped, required this.viewer}) {
|
||||
initialize();
|
||||
}
|
||||
|
||||
final _initialized = Completer<bool>();
|
||||
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) {
|
||||
@@ -283,16 +259,19 @@ class GizmoInputHandler extends InputHandler {
|
||||
_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
|
||||
Stream get cameraUpdated => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Future dispose() async {
|
||||
await viewer.removeEntity(_gizmos[GizmoType.rotation]!._gizmo);
|
||||
await viewer.removeEntity(_gizmos[GizmoType.translation]!._gizmo);
|
||||
_gizmos[GizmoType.rotation]!.dispose();
|
||||
_gizmos[GizmoType.translation]!.dispose();
|
||||
_gizmos.clear();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -328,13 +307,13 @@ class GizmoInputHandler extends InputHandler {
|
||||
|
||||
await viewer.pick(localPosition.x.toInt(), localPosition.y.toInt(),
|
||||
(result) async {
|
||||
if (active?._gizmo.isNonPickable(result.entity) == true ||
|
||||
if (_active?._gizmo.isNonPickable(result.entity) == true ||
|
||||
result.entity == FILAMENT_ENTITY_NULL) {
|
||||
await active!.detach();
|
||||
_pickResultController.add(null);
|
||||
return;
|
||||
}
|
||||
if (active?._gizmo.isGizmoEntity(result.entity) != true) {
|
||||
active!.attach(result.entity);
|
||||
if (_active?._gizmo.isGizmoEntity(result.entity) != true) {
|
||||
_pickResultController.add(result.entity);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -344,18 +323,18 @@ class GizmoInputHandler extends InputHandler {
|
||||
if (!_initialized.isCompleted) {
|
||||
return;
|
||||
}
|
||||
active?.checkHover(localPosition.x.floor(), localPosition.y.floor());
|
||||
_active?.checkHover(localPosition.x.floor(), localPosition.y.floor());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onPointerMove(
|
||||
Vector2 localPosition, Vector2 delta, bool isMiddle) async {
|
||||
if (!isMiddle && active?._active != null) {
|
||||
if (!isMiddle && _active?._active != null) {
|
||||
final scaledDelta = Vector2(
|
||||
delta.x,
|
||||
delta.y,
|
||||
);
|
||||
active!._updateTransform(localPosition, scaledDelta);
|
||||
_active!._updateTransform(localPosition, scaledDelta);
|
||||
return;
|
||||
}
|
||||
return wrapped.onPointerMove(localPosition, delta, isMiddle);
|
||||
@@ -401,18 +380,4 @@ class GizmoInputHandler extends InputHandler {
|
||||
void setActionForType(InputType gestureType, InputAction gestureAction) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
Future detach(ThermionAsset asset) async {
|
||||
if (active?._attachedTo == asset.entity) {
|
||||
await active!.detach();
|
||||
return;
|
||||
}
|
||||
final childEntities = await asset.getChildEntities();
|
||||
for (final childEntity in childEntities) {
|
||||
if (active?._attachedTo == childEntity) {
|
||||
await active!.detach();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
// }
|
||||
|
||||
// Future dispose() async {
|
||||
// await viewer.removeEntity(_translationGizmo);
|
||||
// await viewer.removeAsset(_translationGizmo);
|
||||
// }
|
||||
|
||||
// @override
|
||||
|
||||
@@ -65,9 +65,9 @@ class OverTheShoulderCameraDelegate implements InputHandlerDelegate {
|
||||
static bool get executing => _executing;
|
||||
|
||||
@override
|
||||
Future<void> execute() async {
|
||||
Future<Matrix4?> execute() async {
|
||||
if (_executing) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
_executing = true;
|
||||
@@ -76,7 +76,7 @@ class OverTheShoulderCameraDelegate implements InputHandlerDelegate {
|
||||
_queuedZoomDelta == 0.0 &&
|
||||
_queuedMoveDelta.length2 == 0.0) {
|
||||
_executing = false;
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
Matrix4 currentPlayerTransform = await viewer.getWorldTransform(player);
|
||||
@@ -97,10 +97,8 @@ class OverTheShoulderCameraDelegate implements InputHandlerDelegate {
|
||||
|
||||
// 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;
|
||||
double deltaX = _queuedRotationDelta.x * rotationSensitivity;
|
||||
double deltaY = _queuedRotationDelta.y * rotationSensitivity;
|
||||
|
||||
cameraLookAt = Matrix4.rotationY(-deltaX) *
|
||||
Matrix4.rotationX(-deltaY) *
|
||||
@@ -118,5 +116,6 @@ class OverTheShoulderCameraDelegate implements InputHandlerDelegate {
|
||||
[camera.getEntity(), player], [newCameraTransform, newPlayerTransform]);
|
||||
onUpdate?.call(newPlayerTransform);
|
||||
_executing = false;
|
||||
return newCameraTransform;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:thermion_dart/thermion_dart.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
|
||||
enum InputType {
|
||||
@@ -30,7 +28,11 @@ enum PhysicalKey { W, A, S, D }
|
||||
enum InputAction { TRANSLATE, ROTATE, PICK, ZOOM, NONE }
|
||||
|
||||
abstract class InputHandler {
|
||||
Stream get cameraUpdated;
|
||||
|
||||
@Deprecated("Use @transformUpdated instead")
|
||||
Stream get cameraUpdated => transformUpdated;
|
||||
|
||||
Stream<Matrix4> get transformUpdated;
|
||||
|
||||
Future<void> onPointerHover(Vector2 localPosition, Vector2 delta);
|
||||
Future<void> onPointerScroll(Vector2 localPosition, double scrollDelta);
|
||||
@@ -59,5 +61,4 @@ abstract class InputHandler {
|
||||
void keyDown(PhysicalKey key);
|
||||
void keyUp(PhysicalKey key);
|
||||
|
||||
// Future setCamera(Camera camera);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user