feat: camera and resizing improvements

This commit is contained in:
Nick Fisher
2024-10-02 16:47:55 +08:00
parent d294938a2c
commit 562ecf2ee5
27 changed files with 840 additions and 613 deletions

View File

@@ -9,6 +9,10 @@ import 'implementations/free_flight_camera_delegate.dart';
class DelegateInputHandler implements InputHandler {
final ThermionViewer viewer;
Stream<List<InputType>> get gestures => _gesturesController.stream;
final _gesturesController = StreamController<List<InputType>>.broadcast();
final _logger = Logger("DelegateInputHandler");
InputHandlerDelegate? transformDelegate;
@@ -54,7 +58,7 @@ class DelegateInputHandler implements InputHandler {
factory DelegateInputHandler.fixedOrbit(ThermionViewer viewer,
{double minimumDistance = 10.0,
double? Function(Vector3)? getDistanceToTarget,
Future<double?> Function(Vector3)? getDistanceToTarget,
ThermionEntity? entity,
PickDelegate? pickDelegate}) =>
DelegateInputHandler(
@@ -105,39 +109,51 @@ class DelegateInputHandler implements InputHandler {
await transformDelegate?.queue(action, vector);
}
final keyTypes = <InputType>[];
for (final key in _pressedKeys) {
InputAction? keyAction;
InputType? keyType = null;
Vector3? vector;
switch (key) {
case PhysicalKey.W:
keyAction = _actions[InputType.KEYDOWN_W];
keyType = InputType.KEYDOWN_W;
vector = Vector3(0, 0, -1);
break;
case PhysicalKey.A:
keyAction = _actions[InputType.KEYDOWN_A];
keyType = InputType.KEYDOWN_A;
vector = Vector3(-1, 0, 0);
break;
case PhysicalKey.S:
keyAction = _actions[InputType.KEYDOWN_S];
keyType = InputType.KEYDOWN_S;
vector = Vector3(0, 0, 1);
break;
case PhysicalKey.D:
keyAction = _actions[InputType.KEYDOWN_D];
keyType = InputType.KEYDOWN_D;
vector = Vector3(1, 0, 0);
break;
}
if (keyAction != null) {
var transform = _axes[keyAction];
if (transform != null) {
vector = transform * vector;
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);
}
transformDelegate?.queue(keyAction, vector!);
}
}
await transformDelegate?.execute();
var updates = _inputDeltas.keys.followedBy(keyTypes).toList();
if(updates.isNotEmpty) {
_gesturesController.add(updates);
}
_inputDeltas.clear();
_processing = false;
}

View File

@@ -4,12 +4,13 @@ import '../../../viewer/src/shared_types/camera.dart';
import '../../../viewer/viewer.dart';
import '../../input.dart';
import '../input_handler.dart';
import 'dart:math';
class FixedOrbitRotateInputHandlerDelegate implements InputHandlerDelegate {
final ThermionViewer viewer;
late Future<Camera> _camera;
final double minimumDistance;
double? Function(Vector3)? getDistanceToTarget;
Future<double?> Function(Vector3)? getDistanceToTarget;
Vector2 _queuedRotationDelta = Vector2.zero();
double _queuedZoomDelta = 0.0;
@@ -22,7 +23,14 @@ class FixedOrbitRotateInputHandlerDelegate implements InputHandlerDelegate {
this.getDistanceToTarget,
this.minimumDistance = 10.0,
}) {
_camera = viewer.getMainCamera();
_camera = viewer.getMainCamera().then((Camera cam) async {
var viewMatrix = makeViewMatrix(Vector3(0.0, 0, -minimumDistance),
Vector3.zero(), Vector3(0.0, 1.0, 0.0));
viewMatrix.invert();
await cam.setTransform(viewMatrix);
return cam;
});
}
void dispose() {
@@ -50,12 +58,20 @@ class FixedOrbitRotateInputHandlerDelegate implements InputHandlerDelegate {
}
}
bool _executing = false;
@override
Future<void> execute() async {
if (_queuedRotationDelta.length2 == 0.0 && _queuedZoomDelta == 0.0) {
return;
}
if (_executing) {
return;
}
_executing = true;
final view = await viewer.getViewAt(0);
final viewport = await view.getViewport();
@@ -66,15 +82,22 @@ class FixedOrbitRotateInputHandlerDelegate implements InputHandlerDelegate {
Vector3 currentPosition = modelMatrix.getTranslation();
Vector3 forward = -currentPosition.normalized();
if (forward.length == 0) {
forward = Vector3(0, 0, -1);
currentPosition = Vector3(0, 0, minimumDistance);
}
Vector3 right = _up.cross(forward).normalized();
Vector3 up = forward.cross(right);
// Calculate intersection point and depth
double radius = getDistanceToTarget?.call(currentPosition) ?? 1.0;
if (radius != 1.0) {
radius = currentPosition.length - radius;
}
Vector3 intersection = (-forward).scaled(radius);
// Calculate the point where the camera forward ray intersects with the
// surface of the target sphere
var distanceToTarget =
(await getDistanceToTarget?.call(currentPosition)) ?? 0;
Vector3 intersection =
(-forward).scaled(currentPosition.length - distanceToTarget);
final intersectionInViewSpace = viewMatrix *
Vector4(intersection.x, intersection.y, intersection.z, 1.0);
@@ -98,17 +121,45 @@ class FixedOrbitRotateInputHandlerDelegate implements InputHandlerDelegate {
var worldSpace3 = worldSpace.xyz.normalized() * currentPosition.length;
currentPosition = worldSpace3;
// Apply zoom
// Zoom
if (_queuedZoomDelta != 0.0) {
Vector3 toSurface = currentPosition - intersection;
currentPosition =
currentPosition + toSurface.scaled(_queuedZoomDelta * 0.1);
}
var distToIntersection =
(currentPosition - intersection).length - minimumDistance;
// Ensure minimum distance
if (currentPosition.length < radius + minimumDistance) {
currentPosition =
(currentPosition.normalized() * (radius + minimumDistance));
// if we somehow overshot the minimum distance, reset the camera to the minimum distance
if (distToIntersection < 0) {
currentPosition +=
(intersection.normalized().scaled(-distToIntersection * 10));
} else {
bool zoomingOut = _queuedZoomDelta > 0;
late Vector3 offset;
// when zooming, we don't always use fractions of the distance from
// the camera to the target (this is due to float precision issues at
// large distances, and also it doesn't work well for UI).
// if we're zooming out and the distance is less than 10m, we zoom out by 1 unit
if (zoomingOut) {
if (distToIntersection < 10) {
offset = intersection.normalized();
} else {
offset = intersection.normalized().scaled(distToIntersection / 10);
}
// if we're zooming in and the distance is less than 5m, zoom in by 1/2 the distance,
// otherwise 1/10 of the distance each time
} else {
if (distToIntersection < 5) {
offset = intersection.normalized().scaled(-distToIntersection / 2);
} else {
offset = intersection.normalized().scaled(-distToIntersection / 10);
}
if (offset.length > distToIntersection) {
offset = Vector3.zero();
}
}
currentPosition += offset;
}
}
// Calculate view matrix
@@ -126,5 +177,7 @@ class FixedOrbitRotateInputHandlerDelegate implements InputHandlerDelegate {
// Reset queued deltas
_queuedRotationDelta = Vector2.zero();
_queuedZoomDelta = 0.0;
_executing = false;
}
}

View File

@@ -27,11 +27,13 @@ enum PhysicalKey { W, A, S, D }
enum InputAction { TRANSLATE, ROTATE, PICK, NONE }
abstract class InputHandler {
Stream<List<InputType>> get gestures;
Future<void> onPointerHover(Vector2 localPosition, Vector2 delta);
Future<void> onPointerScroll(Vector2 localPosition, double scrollDelta);
Future<void> onPointerDown(Vector2 localPosition, bool isMiddle);
Future<void> onPointerMove(Vector2 localPosition, Vector2 delta, bool isMiddle);
Future<void> onPointerMove(
Vector2 localPosition, Vector2 delta, bool isMiddle);
Future<void> onPointerUp(bool isMiddle);
Future<void> onScaleStart();
Future<void> onScaleUpdate();