(flutter) provide nicer implementation of FixedOrbitCameraRotationDelegate
This commit is contained in:
@@ -50,20 +50,17 @@ class DelegateGestureHandler implements ThermionGestureHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
factory DelegateGestureHandler.fixedOrbit(ThermionViewer viewer,
|
factory DelegateGestureHandler.fixedOrbit(ThermionViewer viewer,
|
||||||
{double? Function(Vector3)? getDistanceToTarget,
|
{double minimumDistance = 10.0,
|
||||||
double rotationSensitivity = 0.001,
|
double? Function(Vector3)? getDistanceToTarget,
|
||||||
double zoomSensitivity = 0.001,
|
|
||||||
double baseAnglePerMeterNumerator = 10000,
|
|
||||||
PickDelegate? pickDelegate}) =>
|
PickDelegate? pickDelegate}) =>
|
||||||
DelegateGestureHandler(
|
DelegateGestureHandler(
|
||||||
viewer: viewer,
|
viewer: viewer,
|
||||||
pickDelegate: pickDelegate,
|
pickDelegate: pickDelegate,
|
||||||
cameraDelegate: FixedOrbitRotateCameraDelegate(viewer,
|
cameraDelegate: FixedOrbitRotateCameraDelegate(viewer,
|
||||||
getDistanceToTarget: getDistanceToTarget,
|
getDistanceToTarget: getDistanceToTarget,
|
||||||
rotationSensitivity: rotationSensitivity,
|
minimumDistance: minimumDistance),
|
||||||
baseAnglePerMeterNumerator: baseAnglePerMeterNumerator,
|
|
||||||
zoomSensitivity: zoomSensitivity),
|
|
||||||
velocityDelegate: DefaultVelocityDelegate(),
|
velocityDelegate: DefaultVelocityDelegate(),
|
||||||
|
actions: {GestureType.MMB_HOLD_AND_MOVE:GestureAction.ROTATE_CAMERA}
|
||||||
);
|
);
|
||||||
|
|
||||||
factory DelegateGestureHandler.flight(ThermionViewer viewer,
|
factory DelegateGestureHandler.flight(ThermionViewer viewer,
|
||||||
@@ -193,6 +190,7 @@ class DelegateGestureHandler implements ThermionGestureHandler {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
_logger.warning("Error during scroll accumulation: $e");
|
_logger.warning("Error during scroll accumulation: $e");
|
||||||
}
|
}
|
||||||
|
await _applyAccumulatedUpdates();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,48 +1,36 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:ui';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'package:flutter/src/services/keyboard_key.g.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
|
import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
|
||||||
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/default_zoom_camera_delegate.dart';
|
|
||||||
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/delegates.dart';
|
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/delegates.dart';
|
||||||
import 'package:vector_math/vector_math_64.dart';
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
|
/// A camera delegate that rotates the camera around the origin.
|
||||||
|
/// Panning is not permitted; zooming is permitted (up to a minimum distance)
|
||||||
|
///
|
||||||
|
/// The rotation sensitivity will be automatically adjusted so that
|
||||||
|
/// 100 horizontal pixels equates to a geodetic distance of 1m when the camera
|
||||||
|
/// is 1m from the surface (denoted by distanceToSurface). This scales to 10m
|
||||||
|
/// geodetic distance when the camera is 100m from the surface, 100m when the
|
||||||
|
/// camera is 1000m from the surface, and so on.
|
||||||
|
///
|
||||||
|
///
|
||||||
class FixedOrbitRotateCameraDelegate implements CameraDelegate {
|
class FixedOrbitRotateCameraDelegate implements CameraDelegate {
|
||||||
final ThermionViewer viewer;
|
final ThermionViewer viewer;
|
||||||
|
final double minimumDistance;
|
||||||
double rotationSensitivity = 0.01;
|
double? Function(Vector3)? getDistanceToTarget;
|
||||||
|
|
||||||
late DefaultZoomCameraDelegate _zoomCameraDelegate;
|
|
||||||
|
|
||||||
Offset _accumulatedRotationDelta = Offset.zero;
|
Offset _accumulatedRotationDelta = Offset.zero;
|
||||||
double _accumulatedZoomDelta = 0.0;
|
double _accumulatedZoomDelta = 0.0;
|
||||||
|
|
||||||
static final _up = Vector3(0, 1, 0);
|
static final _up = Vector3(0, 1, 0);
|
||||||
|
|
||||||
Timer? _updateTimer;
|
Timer? _updateTimer;
|
||||||
|
|
||||||
Vector3 _targetPosition = Vector3(0, 0, 0);
|
FixedOrbitRotateCameraDelegate(
|
||||||
|
this.viewer, {
|
||||||
double? Function(Vector3)? getDistanceToTarget;
|
this.getDistanceToTarget,
|
||||||
|
this.minimumDistance = 10.0,
|
||||||
FixedOrbitRotateCameraDelegate(this.viewer,
|
});
|
||||||
{this.getDistanceToTarget,
|
|
||||||
double? rotationSensitivity,
|
|
||||||
double zoomSensitivity = 0.005}) {
|
|
||||||
_zoomCameraDelegate = DefaultZoomCameraDelegate(this.viewer,
|
|
||||||
zoomSensitivity: zoomSensitivity,
|
|
||||||
getDistanceToTarget: getDistanceToTarget);
|
|
||||||
this.rotationSensitivity = rotationSensitivity ?? 0.01;
|
|
||||||
_startUpdateTimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _startUpdateTimer() {
|
|
||||||
_updateTimer = Timer.periodic(const Duration(milliseconds: 16), (_) {
|
|
||||||
_applyAccumulatedUpdates();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_updateTimer?.cancel();
|
_updateTimer?.cancel();
|
||||||
@@ -51,6 +39,7 @@ class FixedOrbitRotateCameraDelegate implements CameraDelegate {
|
|||||||
@override
|
@override
|
||||||
Future<void> rotate(Offset delta, Vector2? velocity) async {
|
Future<void> rotate(Offset delta, Vector2? velocity) async {
|
||||||
_accumulatedRotationDelta += delta;
|
_accumulatedRotationDelta += delta;
|
||||||
|
await _applyAccumulatedUpdates();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -60,11 +49,8 @@ class FixedOrbitRotateCameraDelegate implements CameraDelegate {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> zoom(double yScrollDeltaInPixels, Vector2? velocity) async {
|
Future<void> zoom(double yScrollDeltaInPixels, Vector2? velocity) async {
|
||||||
if (yScrollDeltaInPixels > 1) {
|
_accumulatedZoomDelta += yScrollDeltaInPixels > 0 ? 1 : -1;
|
||||||
_accumulatedZoomDelta++;
|
await _applyAccumulatedUpdates();
|
||||||
} else {
|
|
||||||
_accumulatedZoomDelta--;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _applyAccumulatedUpdates() async {
|
Future<void> _applyAccumulatedUpdates() async {
|
||||||
@@ -73,64 +59,82 @@ class FixedOrbitRotateCameraDelegate implements CameraDelegate {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var viewMatrix = await viewer.getCameraViewMatrix();
|
||||||
var modelMatrix = await viewer.getCameraModelMatrix();
|
var modelMatrix = await viewer.getCameraModelMatrix();
|
||||||
Vector3 cameraPosition = modelMatrix.getTranslation();
|
var projectionMatrix = await viewer.getCameraProjectionMatrix();
|
||||||
|
var inverseProjectionMatrix = projectionMatrix.clone()..invert();
|
||||||
|
Vector3 currentPosition = modelMatrix.getTranslation();
|
||||||
|
|
||||||
final heightAboveSurface = getDistanceToTarget?.call(cameraPosition) ?? 1.0;
|
Vector3 forward = -currentPosition.normalized();
|
||||||
|
Vector3 right = _up.cross(forward).normalized();
|
||||||
|
Vector3 up = forward.cross(right);
|
||||||
|
|
||||||
final sphereRadius = cameraPosition.length - heightAboveSurface;
|
// first, we find the point in the sphere that intersects with the camera
|
||||||
|
// forward vector
|
||||||
// Apply rotation
|
double radius = 0.0;
|
||||||
if (_accumulatedRotationDelta.distanceSquared > 0) {
|
double? distanceToTarget = getDistanceToTarget?.call(currentPosition);
|
||||||
// Calculate the distance factor
|
if (distanceToTarget != null) {
|
||||||
final distanceFactor = sqrt((heightAboveSurface / sphereRadius) + 1);
|
radius = currentPosition.length - distanceToTarget;
|
||||||
|
} else {
|
||||||
// Adjust the base angle per meter
|
radius = 1.0;
|
||||||
final baseAnglePerMeter = 10000 / sphereRadius;
|
|
||||||
final adjustedAnglePerMeter = baseAnglePerMeter * distanceFactor;
|
|
||||||
|
|
||||||
final metersOnSurface = _accumulatedRotationDelta;
|
|
||||||
final rotationX = metersOnSurface.dy * adjustedAnglePerMeter;
|
|
||||||
final rotationY = metersOnSurface.dx * adjustedAnglePerMeter;
|
|
||||||
|
|
||||||
Matrix4 rotation = Matrix4.rotationX(rotationX)..rotateY(rotationY);
|
|
||||||
Vector3 newPos = rotation.getRotation() * cameraPosition;
|
|
||||||
cameraPosition = newPos;
|
|
||||||
}
|
}
|
||||||
|
Vector3 intersection = (-forward).scaled(radius);
|
||||||
|
|
||||||
// Normalize the position to maintain constant distance from center
|
// next, calculate the depth value at that intersection point
|
||||||
cameraPosition =
|
final intersectionInViewSpace = viewMatrix *
|
||||||
cameraPosition.normalized() * (sphereRadius + heightAboveSurface);
|
Vector4(intersection.x, intersection.y, intersection.z, 1.0);
|
||||||
|
final intersectionInClipSpace = projectionMatrix * intersectionInViewSpace;
|
||||||
|
final intersectionInNdcSpace =
|
||||||
|
intersectionInClipSpace / intersectionInClipSpace.w;
|
||||||
|
|
||||||
// Apply zoom (modified to ensure minimum 10m distance)
|
// using that depth value, find the world space position of the mouse
|
||||||
|
// note we flip the signs of the X and Y values
|
||||||
|
|
||||||
|
final ndcX = 2 *
|
||||||
|
((-_accumulatedRotationDelta.dx * viewer.pixelRatio) /
|
||||||
|
viewer.viewportDimensions.$1);
|
||||||
|
final ndcY = 2 *
|
||||||
|
((_accumulatedRotationDelta.dy * viewer.pixelRatio) /
|
||||||
|
viewer.viewportDimensions.$2);
|
||||||
|
final ndc = Vector4(ndcX, ndcY, intersectionInNdcSpace.z, 1.0);
|
||||||
|
|
||||||
|
var clipSpace = Vector4(
|
||||||
|
ndc.x * intersectionInClipSpace.w,
|
||||||
|
ndcY * intersectionInClipSpace.w,
|
||||||
|
ndc.z * intersectionInClipSpace.w,
|
||||||
|
intersectionInClipSpace.w);
|
||||||
|
Vector4 cameraSpace = inverseProjectionMatrix * clipSpace;
|
||||||
|
Vector4 worldSpace = modelMatrix * cameraSpace;
|
||||||
|
|
||||||
|
// the new camera world space position will be that position,
|
||||||
|
// scaled to the camera's current distance
|
||||||
|
var worldSpace3 = worldSpace.xyz.normalized() * currentPosition.length;
|
||||||
|
currentPosition = worldSpace3;
|
||||||
|
|
||||||
|
// Apply zoom
|
||||||
if (_accumulatedZoomDelta != 0.0) {
|
if (_accumulatedZoomDelta != 0.0) {
|
||||||
var zoomFactor = -0.5 * _accumulatedZoomDelta;
|
// double zoomFactor = 1.0 + ();
|
||||||
|
Vector3 toSurface = currentPosition - intersection;
|
||||||
double newHeight = heightAboveSurface * (1 - zoomFactor);
|
currentPosition = currentPosition + toSurface.scaled(_accumulatedZoomDelta * 0.1);
|
||||||
newHeight = newHeight.clamp(
|
|
||||||
10.0, double.infinity); // Prevent getting closer than 10m to surface
|
|
||||||
cameraPosition = cameraPosition.normalized() * (sphereRadius + newHeight);
|
|
||||||
|
|
||||||
_accumulatedZoomDelta = 0.0;
|
_accumulatedZoomDelta = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure minimum 10m distance even after rotation
|
// Ensure minimum distance
|
||||||
final currentHeight = cameraPosition.length - sphereRadius;
|
if (currentPosition.length < radius + minimumDistance) {
|
||||||
if (currentHeight < 10.0) {
|
currentPosition =
|
||||||
cameraPosition = cameraPosition.normalized() * (sphereRadius + 10.0);
|
(currentPosition.normalized() * (radius + minimumDistance));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate view matrix (unchanged)
|
// Calculate view matrix
|
||||||
Vector3 forward = cameraPosition.normalized();
|
forward = -currentPosition.normalized();
|
||||||
Vector3 up = Vector3(0, 1, 0);
|
right = _up.cross(forward).normalized();
|
||||||
final right = up.cross(forward)..normalize();
|
|
||||||
up = forward.cross(right);
|
up = forward.cross(right);
|
||||||
|
|
||||||
Matrix4 viewMatrix = makeViewMatrix(cameraPosition, Vector3.zero(), up);
|
Matrix4 newViewMatrix = makeViewMatrix(currentPosition, Vector3.zero(), up);
|
||||||
viewMatrix.invert();
|
newViewMatrix.invert();
|
||||||
|
|
||||||
// Set the camera model matrix
|
// Set the camera model matrix
|
||||||
await viewer.setCameraModelMatrix4(viewMatrix);
|
await viewer.setCameraModelMatrix4(newViewMatrix);
|
||||||
_accumulatedRotationDelta = Offset.zero;
|
_accumulatedRotationDelta = Offset.zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user