refactor: gizmo/input handler improvements

This commit is contained in:
Nick Fisher
2024-12-23 14:37:53 +08:00
parent 5e89dc43e8
commit 584ace23b4
8 changed files with 117 additions and 142 deletions

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -23,7 +23,7 @@
// }
// Future dispose() async {
// await viewer.removeEntity(_translationGizmo);
// await viewer.removeAsset(_translationGizmo);
// }
// @override

View File

@@ -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;
}
}

View File

@@ -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);
}