Add native touch rotation to FlyByCamera - #2943
riccardobl with Copilot wants to merge 4 commits into
Conversation
Co-authored-by: riccardobl <4943530+riccardobl@users.noreply.github.com>
Co-authored-by: riccardobl <4943530+riccardobl@users.noreply.github.com>
| if (!enabled || !name.equals(FLYCAM_TOUCH) || event.getType() != TouchEvent.Type.MOVE) { | ||
| return; | ||
| } | ||
|
|
There was a problem hiding this comment.
Multi-touch is not handled: AndroidTouchInput.onTouch() converts every pointer in an ACTION_MOVE into its own MOVE event, so with two fingers moving both deltas are applied here and the camera rotates at roughly double speed / jerks. The emulated-mouse path explicitly avoids this (generateMouseEvent() returns null when numPointers > 1), and TouchTrigger(TouchInput.ALL) delivers all pointers to this listener.
Suggested fix: latch the pointer that started the gesture on DOWN and ignore MOVE events from other pointers (DOWN/UP also have keyCode == 0, so they reach this mapping):
private int touchPointerId = -1;
...
case DOWN: if (touchPointerId == -1) touchPointerId = event.getPointerId(); break;
case UP: if (event.getPointerId() == touchPointerId) touchPointerId = -1; break;
case MOVE: if (event.getPointerId() != touchPointerId) return; // then rotateThere was a problem hiding this comment.
add a code suggestion to fix this issue
There was a problem hiding this comment.
Committable minimal fix: drop the secondary pointers before the deltas are applied.
| // Only the initial (primary) finger may rotate the camera: AndroidTouchInput | |
| // emits one MOVE event per pointer, so without this filter every finger's | |
| // delta would reach the two rotateCamera() calls below. | |
| if (event.getPointerId() != 0) { | |
| return; | |
| } | |
Android assigns pointer id 0 to the first finger of a gesture (event.getPointerId(getPointerIndex(event)) in AndroidTouchInput.onTouch), and TouchEvent defaults pointerId to 0, so existing callers and the current tests keep working. Trade-off: if the primary finger is lifted while a second finger keeps dragging, rotation pauses until the next gesture (a new DOWN with id 0).
The stricter variant described above (latch on DOWN, clear on UP, ignore other pointers) needs a second edit site that cannot be folded into this suggestion: the event.getType() != TouchEvent.Type.MOVE early return must be relaxed so DOWN/UP reach the handler, plus a private int touchPointerId = -1; field next to inputMappingsRegistered. Both hunks are non-adjacent, so a single-suggestion apply is not possible for that version.
There was a problem hiding this comment.
propose both changes with two different suggestions
There was a problem hiding this comment.
Both hunks of the stricter variant, applied together (the switch will not compile without the field). This supersedes the minimal pointerId != 0 variant.
1. State — applied at the field declaration (line 127, next to inputMappingsRegistered):
| private boolean inputMappingsRegistered; | |
| private int touchPointerId = -1; |
2. Guard/dispatch — replaces lines 580–582:
| if (!enabled || !name.equals(FLYCAM_TOUCH)) { | |
| return; | |
| } | |
| switch (event.getType()) { | |
| case DOWN: | |
| if (touchPointerId == -1) { | |
| touchPointerId = event.getPointerId(); | |
| } | |
| return; | |
| case UP: | |
| if (event.getPointerId() == touchPointerId) { | |
| touchPointerId = -1; | |
| } | |
| return; | |
| case MOVE: | |
| if (event.getPointerId() != touchPointerId) { | |
| return; | |
| } | |
| break; | |
| default: | |
| return; | |
| } |
InputManager.onTouchEventQueued() dispatches on evt.getKeyCode(), which is 0 for every DOWN/UP/MOVE/gesture event, so all of them reach the FLYCAM_Touch mapping; the switch is what separates them. Only the finger latched on DOWN rotates (Android assigns pointer id 0 to the first finger of a gesture); if that finger lifts while a second one keeps dragging, rotation pauses until the next DOWN.
Since this thread is anchored at line 583, block 1 has to be applied at the field declaration, not here.
| new TouchEvent(TouchEvent.Type.MOVE, 0f, 0f, 128f, 0f), 0f); | ||
|
|
||
| assertFalse(initialDirection.equals(camera.getDirection())); | ||
| } |
There was a problem hiding this comment.
These tests invoke onTouch() directly, so they pass even if the FLYCAM_Touch mapping, the TouchTrigger, or the TouchListener registration were missing — the wiring this PR fixes is untested, and the direction/scale of the rotation is not asserted either.
Drive the event through the input pipeline instead, e.g. build an InputManager (dummy mouse/key inputs), call flyCam.registerWithInput(inputManager), then inputManager.onTouchEventQueued(new TouchEvent(TouchEvent.Type.MOVE, 0f, 0f, 128f, 0f)) and assert the sign of the resulting yaw change. That also gives you a place to cover the multi-touch case and the non-MOVE event filter.
Also note touchDragRotatesCameraWhenDragToRotateIsEnabled currently asserts exactly the same thing as touchDragRotatesCamera.
jaime-jmebot
left a comment
There was a problem hiding this comment.
The rotation math itself is consistent with the mouse path: TOUCH_ROTATION_SCALE = 1/1024 matches InputManager's abs(dx)/1024, and the signs (yaw on initialUpVec, pitch on getLeft(), invertY handling) mirror FLYCAM_LEFT/RIGHT/UP/DOWN. The two blocking items are the multi-touch handling and the missing coverage of the wiring (see inline comments).
- Multi-touch: Android emits one
MOVEper pointer, so all finger deltas are applied to the rotation; the emulated-mouse path guards against exactly this. - Touch tests bypass
InputManager, so they would pass even with a broken/missingFLYCAM_Touchmapping; they also assert nothing about direction or scale. rotateCamera(..., true)bypasses thedragToRotate && !canRotategate, so touch rotation is always active, regardless ofdragToRotate. If that is intended ("touch drag is the drag"), please document it in the class javadoc (theControls:list still mentions mouse only) and insetDragToRotate(); the PR description ("allow touch dragging when dragToRotate is enabled") reads as if the gate were applied.- If mouse emulation is enabled again (
AppSettings.isEmulateMouse()/InputManager.isSimulateMouse()), a single-finger drag produces both aMouseMotionEventand aTouchEvent, so the camera rotates twice as fast. Worth guarding (e.g. skip touch rotation whileinputManager.isSimulateMouse()).
Android FlyByCamera rotation previously depended on emulated mouse events. With mouse emulation disabled by default, touch drag no longer rotates the camera.
Touch input
TouchTriggerinFlyByCamera.MOVEevents directly throughTouchListener.Rotation behavior
dragToRotateis enabled.