feat: simplify FixedOrbitCameraRotationDelegate

This commit is contained in:
Nick Fisher
2024-10-22 22:23:35 +08:00
parent 68053b0f5a
commit a5c3d7b215

View File

@@ -5,26 +5,30 @@ import '../../../viewer/viewer.dart';
import '../../input.dart';
import '../input_handler.dart';
///
/// An [InputHandlerDelegate] that orbits the camera around a fixed
/// point.
///
class FixedOrbitRotateInputHandlerDelegate implements InputHandlerDelegate {
final ThermionViewer viewer;
late Future<Camera> _camera;
final double minimumDistance;
Future<double?> Function(Vector3)? getDistanceToTarget;
late final Vector3 target;
Vector2 _queuedRotationDelta = Vector2.zero();
double _queuedZoomDelta = 0.0;
static final _up = Vector3(0, 1, 0);
Timer? _updateTimer;
FixedOrbitRotateInputHandlerDelegate(
this.viewer, {
this.getDistanceToTarget,
Vector3? target,
this.minimumDistance = 10.0,
}) {
this.target = target ?? Vector3.zero();
_camera = viewer.getMainCamera().then((Camera cam) async {
var viewMatrix = makeViewMatrix(Vector3(0.0, 0, -minimumDistance),
Vector3.zero(), Vector3(0.0, 1.0, 0.0));
this.target, Vector3(0.0, 1.0, 0.0));
viewMatrix.invert();
await cam.setTransform(viewMatrix);
@@ -81,99 +85,53 @@ class FixedOrbitRotateInputHandlerDelegate implements InputHandlerDelegate {
var inverseProjectionMatrix = projectionMatrix.clone()..invert();
Vector3 currentPosition = modelMatrix.getTranslation();
Vector3 forward = -currentPosition.normalized();
Vector3 forward = modelMatrix.forward;
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 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);
final intersectionInClipSpace = projectionMatrix * intersectionInViewSpace;
final intersectionInNdcSpace =
intersectionInClipSpace / intersectionInClipSpace.w;
// Calculate new camera position based on rotation
final ndcX = 2 * ((-_queuedRotationDelta.x) / viewport.width);
final ndcY = 2 * ((_queuedRotationDelta.y) / viewport.height);
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;
var worldSpace3 = worldSpace.xyz.normalized() * currentPosition.length;
currentPosition = worldSpace3;
// Zoom
if (_queuedZoomDelta != 0.0) {
var distToIntersection =
(currentPosition - intersection).length - minimumDistance;
var newPosition = currentPosition +
(currentPosition - target).scaled(_queuedZoomDelta * 0.1);
var distToTarget = (newPosition - target).length;
// 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;
if (distToTarget >= minimumDistance) {
currentPosition = newPosition;
// Calculate view matrix
forward = (currentPosition - target).normalized();
var right = modelMatrix.up.cross(forward).normalized();
var up = forward.cross(right);
// 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).
Matrix4 newViewMatrix = makeViewMatrix(currentPosition, target, up);
newViewMatrix.invert();
// 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;
await (await _camera).setModelMatrix(newViewMatrix);
}
} else if (_queuedRotationDelta.length != 0) {
double rotateX = _queuedRotationDelta.x * 0.01;
double rotateY = _queuedRotationDelta.y * 0.01;
var modelMatrix = await viewer.getCameraModelMatrix();
// 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());
modelMatrix = rot1 *
rot2 * modelMatrix;
await (await _camera).setModelMatrix(modelMatrix);
}
// Calculate view matrix
forward = -currentPosition.normalized();
right = _up.cross(forward).normalized();
up = forward.cross(right);
Matrix4 newViewMatrix = makeViewMatrix(currentPosition, Vector3.zero(), up);
newViewMatrix.invert();
// Set the camera model matrix
var camera = await _camera;
await camera.setModelMatrix(newViewMatrix);
// Reset queued deltas
_queuedRotationDelta = Vector2.zero();
_queuedZoomDelta = 0.0;