camera fixes for assets with large bounding boxes

This commit is contained in:
Nick Fisher
2023-10-11 14:12:04 +08:00
parent 2923f5907f
commit b7f50df2dc
25 changed files with 4080 additions and 1692 deletions

View File

@@ -197,7 +197,7 @@ abstract class FilamentController {
///
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
///
Future zoomUpdate(double z);
Future zoomUpdate(double x, double y, double z);
///
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.

View File

@@ -191,16 +191,45 @@ class FilamentControllerFFI extends FilamentController {
_isReadyForScene.complete(true);
}
///
/// I'm not exactly sure how to resize the backing textures on all platforms.
/// So for now, I'm sticking with the safe option when the widget is resized: destroying the swapchain, recreating the textures, and creating a new swapchain.
///
@override
Future resize(int width, int height, {double scaleFactor = 1.0}) async {
_resizing = true;
setRendering(false);
_lib.destroy_swap_chain(_viewer!);
await destroyTexture();
size = ui.Size(width * _pixelRatio, height * _pixelRatio);
_textureId = await _channel
.invokeMethod("resize", [size.width, size.height, scaleFactor]);
_textureIdController.add(_textureId);
var textures =
await _channel.invokeMethod("createTexture", [size.width, size.height]);
var flutterTextureId = textures[0];
_textureId = flutterTextureId;
// void* on iOS (pointer to pixel buffer), void* on Android (pointer to native window), null on Windows/macOS
var surfaceAddress = textures[1] as int? ?? 0;
// null on iOS/Android, void* on MacOS (pointer to metal texture), GLuid on Windows/Linux
var nativeTexture = textures[2] as int? ?? 0;
_lib.create_swap_chain_ffi(
_viewer!,
Pointer<Void>.fromAddress(surfaceAddress),
size.width.toInt(),
size.height.toInt());
if (nativeTexture != 0) {
assert(surfaceAddress == 0);
print("Creating render target from native texture $nativeTexture");
_lib.create_render_target_ffi(
_viewer!, nativeTexture, size.width.toInt(), size.height.toInt());
}
_lib.update_viewport_and_camera_projection_ffi(
_viewer!, size.width.toInt(), size.height.toInt(), scaleFactor);
_viewer!, size.width.toInt(), size.height.toInt(), 1.0);
_textureIdController.add(_textureId);
_resizing = false;
setRendering(true);
}
@@ -553,11 +582,11 @@ class FilamentControllerFFI extends FilamentController {
}
@override
Future zoomUpdate(double z) async {
Future zoomUpdate(double x, double y, double z) async {
if (_viewer == null || _resizing) {
throw Exception("No viewer available, ignoring");
}
_lib.scroll_update(_viewer!, 0.0, 0.0, z);
_lib.scroll_update(_viewer!, x, y, z);
}
@override

View File

@@ -434,11 +434,11 @@ class FilamentControllerMethodChannel extends FilamentController {
await _channel.invokeMethod("scrollBegin");
}
Future zoomUpdate(double z) async {
Future zoomUpdate(double x, double y, double z) async {
if (_viewer == null || _resizing) {
throw Exception("No viewer available, ignoring");
}
await _channel.invokeMethod("scrollUpdate", [0.0, 0.0, z]);
await _channel.invokeMethod("scrollUpdate", [x, y, z]);
}
Future zoomEnd() async {

View File

@@ -38,15 +38,12 @@ class FilamentGestureDetector extends StatelessWidget {
///
final bool listenerEnabled;
final double zoomDelta;
const FilamentGestureDetector(
{Key? key,
required this.controller,
this.child,
this.showControlOverlay = false,
this.listenerEnabled = true,
this.zoomDelta = 1})
this.listenerEnabled = true})
: super(key: key);
@override

View File

@@ -32,15 +32,12 @@ class FilamentGestureDetectorDesktop extends StatefulWidget {
///
final bool listenerEnabled;
final double zoomDelta;
const FilamentGestureDetectorDesktop(
{Key? key,
required this.controller,
this.child,
this.showControlOverlay = false,
this.listenerEnabled = true,
this.zoomDelta = 1})
this.listenerEnabled = true})
: super(key: key);
@override
@@ -71,15 +68,18 @@ class _FilamentGestureDetectorDesktopState
///
/// Scroll-wheel on desktop, interpreted as zoom
///
void _zoom(PointerScrollEvent pointerSignal) {
void _zoom(PointerScrollEvent pointerSignal) async {
_scrollTimer?.cancel();
widget.controller.zoomBegin();
widget.controller.zoomUpdate(pointerSignal.scrollDelta.dy > 0
? widget.zoomDelta
: -widget.zoomDelta);
_scrollTimer = Timer(const Duration(milliseconds: 100), () {
widget.controller.zoomEnd();
_scrollTimer = null;
await widget.controller.zoomBegin();
await widget.controller.zoomUpdate(
pointerSignal.localPosition.dx,
pointerSignal.localPosition.dy,
pointerSignal.scrollDelta.dy > 0 ? 1 : -1);
// we don't want to end the zoom in the same frame, because this will destroy the camera manipulator (and cancel the zoom update).
// here, we just defer calling [zoomEnd] for 100ms to ensure the update is propagated through.
_scrollTimer = Timer(Duration(milliseconds: 100), () async {
await widget.controller.zoomEnd();
});
}
@@ -108,7 +108,8 @@ class _FilamentGestureDetectorDesktopState
// if this is the first move event, we need to call rotateStart/panStart to set the first coordinates
if (!_pointerMoving) {
if (d.buttons == kTertiaryButton) {
widget.controller.rotateStart(d.position.dx, d.position.dy);
widget.controller
.rotateStart(d.localPosition.dx, d.localPosition.dy);
} else {
widget.controller
.panStart(d.localPosition.dx, d.localPosition.dy);
@@ -117,7 +118,8 @@ class _FilamentGestureDetectorDesktopState
// set the _pointerMoving flag so we don't call rotateStart/panStart on future move events
_pointerMoving = true;
if (d.buttons == kTertiaryButton) {
widget.controller.rotateUpdate(d.position.dx, d.position.dy);
widget.controller
.rotateUpdate(d.localPosition.dx, d.localPosition.dy);
} else {
widget.controller.panUpdate(d.localPosition.dx, d.localPosition.dy);
}

View File

@@ -158,8 +158,8 @@ class _FilamentGestureDetectorMobileState
},
onScaleUpdate: (ScaleUpdateDetails d) async {
if (d.pointerCount == 2) {
widget.controller
.zoomUpdate(d.horizontalScale > 1 ? 0.1 : -0.1);
widget.controller.zoomUpdate(d.localFocalPoint.dx,
d.localFocalPoint.dy, d.horizontalScale > 1 ? 0.1 : -0.1);
} else if (!_scaling) {
if (_rotateOnPointerMove) {
widget.controller