From 3dcacfe631a180e73b1b0ac51aebdecf5d334b39 Mon Sep 17 00:00:00 2001 From: Nick Fisher Date: Wed, 23 Oct 2024 14:36:26 +0800 Subject: [PATCH] doc: update camera_manipulation example project and docs --- docs/camera_manipulation.mdx | 38 ++++++++++++++- .../flutter/camera_manipulation/lib/main.dart | 47 +++++++++++++++---- 2 files changed, 73 insertions(+), 12 deletions(-) diff --git a/docs/camera_manipulation.mdx b/docs/camera_manipulation.mdx index 79f47394..dea46481 100644 --- a/docs/camera_manipulation.mdx +++ b/docs/camera_manipulation.mdx @@ -16,14 +16,48 @@ This will generally wrap a `ThermionWidget`, meaning the entire viewport will ac Positioned.fill( child: ThermionListenerWidget( inputHandler: - DelegateInputHandler.fixedOrbit(_thermionViewer!), + DelegateInputHandler.fixedOrbit(_thermionViewer!) + ..setActionForType(InputType.MMB_HOLD_AND_MOVE, InputAction.ROTATE) + ..setActionForType(InputType.SCALE1, InputAction.ROTATE) + ..setActionForType(InputType.SCALE2, InputAction.ZOOM) + ..setActionForType(InputType.SCROLLWHEEL, InputAction.ZOOM), child: ThermionWidget( viewer: _thermionViewer!, ))), ]); ``` -`ThermionListenerWidget` is a very simple widget; it simply forwards pointer, gesture and keyboard events to an instance of [InputHandler] that you provide. +`ThermionListenerWidget` is a very simple widget; it simply forwards pointer, gesture and keyboard events to the provided [InputHandler], which must decide how to interpret those events. + +For example, one [InputHandler] implementation might interpret mouse pointer movement as "rotate the camera", whereas a separate implementation might interpret it as "translate this specific entity". + +Thermion provides two default InputHandler implementations for manipulating the camera: [DelegateInputHandler.fixedOrbit] and [DelegateInputHandler.flight]. + +[DelegateInputHandler.fixedOrbit] will rotate the camera in a fixed orbit around a target point (the origin, by default), and also allow zooming in/out (subject to a minimum distance, which is configurable). + +By default, [DelegateInputHandler.fixedOrbit] will: +- rotate the camera when the middle mouse button is held and the pointer is moved (on desktop), and when a single swipe left/right/up/down is detected (on mobile) +- zoom the camera when the scroll wheel is scrolled up/down (on desktop), and when a pinch gesture is detected (on mobile) + +You can change the action for a specific input type by calling `setActionForType`; for example, if you wanted to rotate the camera by moving the mouse pointer while holding the left mouse button, you would call: + +``` +setActionForType(InputType.LMB_HOLD_AND_MOVE, InputAction.ROTATE) +``` + +See the [InputType] and [InputAction] enums for available input types and actions. + +[DelegateInputHandler.flight] will translate keyboard and mouse/touchscreen gestures to free flight camera manipulation. + +By default: +- holding the middle mouse button will control the pitch/roll/yaw of the camera +- holding the left mouse button will pan the camera left/right/up/down +- the middle mouse button will zoom/dolly the camera in/out +- the WASD keys will pan the camera left/right/up/down and dolly the camera forward/backward + +If these don't exactly fit your use case, you can create your own [InputHandler] implementation. If you think it would be useful to other users, please feel free to submit a PR for your implementation to be included in the base Thermion package. + + diff --git a/examples/flutter/camera_manipulation/lib/main.dart b/examples/flutter/camera_manipulation/lib/main.dart index 4563c6b6..d0b4b2a7 100644 --- a/examples/flutter/camera_manipulation/lib/main.dart +++ b/examples/flutter/camera_manipulation/lib/main.dart @@ -36,6 +36,9 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { + late DelegateInputHandler _fixedOrbitInputHandler; + late DelegateInputHandler _freeFlightInputHandler; + @override void initState() { super.initState(); @@ -48,27 +51,51 @@ class _MyHomePageState extends State { await _thermionViewer!.setPostProcessing(true); await _thermionViewer!.setRendering(true); + _fixedOrbitInputHandler = + DelegateInputHandler.fixedOrbit(_thermionViewer!) + ..setActionForType(InputType.MMB_HOLD_AND_MOVE, InputAction.ROTATE) + ..setActionForType(InputType.SCALE1, InputAction.ROTATE) + ..setActionForType(InputType.SCALE2, InputAction.ZOOM) + ..setActionForType(InputType.SCROLLWHEEL, InputAction.ZOOM); + + _freeFlightInputHandler = + DelegateInputHandler.flight(_thermionViewer!) + ..setActionForType(InputType.MMB_HOLD_AND_MOVE, InputAction.ROTATE) + ..setActionForType(InputType.SCALE1, InputAction.ROTATE) + ..setActionForType(InputType.SCALE2, InputAction.ZOOM) + ..setActionForType(InputType.SCROLLWHEEL, InputAction.ZOOM); + setState(() {}); }); } ThermionViewer? _thermionViewer; + bool isOrbit = true; + @override Widget build(BuildContext context) { return Stack(children: [ - if (_thermionViewer != null) + if (_thermionViewer != null) ...[ Positioned.fill( child: ThermionListenerWidget( - inputHandler: - DelegateInputHandler.fixedOrbit(_thermionViewer!) - ..setActionForType(InputType.MMB_HOLD_AND_MOVE, InputAction.ROTATE) - ..setActionForType(InputType.SCALE1, InputAction.ROTATE) - ..setActionForType(InputType.SCALE2, InputAction.ZOOM) - ..setActionForType(InputType.SCROLLWHEEL, InputAction.ZOOM) , - child: ThermionWidget( - viewer: _thermionViewer!, - ))), + inputHandler: isOrbit + ? _fixedOrbitInputHandler : _freeFlightInputHandler, + child:ThermionWidget( + viewer: _thermionViewer!, + ))), + Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + ElevatedButton( + onPressed: () { + isOrbit = !isOrbit; + setState(() {}); + }, + child: Text("Switch to ${isOrbit ? "Free Flight" : "Orbit"}")) + ], + ) + ], ]); } }