diff --git a/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java b/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java index 8e176b85e9..cba0de8113 100644 --- a/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java +++ b/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java @@ -37,6 +37,9 @@ import com.jme3.input.controls.KeyTrigger; import com.jme3.input.controls.MouseAxisTrigger; import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.input.controls.TouchListener; +import com.jme3.input.controls.TouchTrigger; +import com.jme3.input.event.TouchEvent; import com.jme3.math.Matrix3f; import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; @@ -54,12 +57,14 @@ * - WASD keys for moving forward/backward and strafing * - QZ keys raise or lower the camera */ -public class FlyByCamera implements AnalogListener, ActionListener, JoystickConnectionListener { +public class FlyByCamera implements AnalogListener, ActionListener, JoystickConnectionListener, TouchListener { private static final String FLYCAM_JOYSTICK_LEFT = "FLYCAM_JoystickLeft"; private static final String FLYCAM_JOYSTICK_RIGHT = "FLYCAM_JoystickRight"; private static final String FLYCAM_JOYSTICK_UP = "FLYCAM_JoystickUp"; private static final String FLYCAM_JOYSTICK_DOWN = "FLYCAM_JoystickDown"; + private static final String FLYCAM_TOUCH = "FLYCAM_Touch"; + private static final float TOUCH_ROTATION_SCALE = 1f / 1024f; private static final String[] mappings = new String[]{ CameraInput.FLYCAM_LEFT, @@ -84,7 +89,8 @@ public class FlyByCamera implements AnalogListener, ActionListener, JoystickConn FLYCAM_JOYSTICK_LEFT, FLYCAM_JOYSTICK_RIGHT, FLYCAM_JOYSTICK_UP, - FLYCAM_JOYSTICK_DOWN + FLYCAM_JOYSTICK_DOWN, + FLYCAM_TOUCH }; /** * camera controlled by this controller (not null) @@ -306,6 +312,7 @@ private void registerInputMappings() { inputManager.addMapping(CameraInput.FLYCAM_ZOOMIN, new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false)); inputManager.addMapping(CameraInput.FLYCAM_ZOOMOUT, new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true)); inputManager.addMapping(CameraInput.FLYCAM_ROTATEDRAG, new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addMapping(FLYCAM_TOUCH, new TouchTrigger(TouchInput.ALL)); // keyboard only WASD for movement and WZ for rise/lower height inputManager.addMapping(CameraInput.FLYCAM_STRAFELEFT, new KeyTrigger(KeyInput.KEY_A)); @@ -567,4 +574,14 @@ public void onAction(String name, boolean isPressed, float tpf) { } } } + + @Override + public void onTouch(String name, TouchEvent event, float tpf) { + if (!enabled || !name.equals(FLYCAM_TOUCH) || event.getType() != TouchEvent.Type.MOVE) { + return; + } + + rotateCamera(-event.getDeltaX() * TOUCH_ROTATION_SCALE, initialUpVec, true); + rotateCamera(-event.getDeltaY() * TOUCH_ROTATION_SCALE * (invertY ? -1 : 1), cam.getLeft(tempLeft), true); + } } diff --git a/jme3-core/src/test/java/com/jme3/input/FlyByCameraTest.java b/jme3-core/src/test/java/com/jme3/input/FlyByCameraTest.java new file mode 100644 index 0000000000..f978965eed --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/input/FlyByCameraTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +import com.jme3.input.event.TouchEvent; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +class FlyByCameraTest { + + @Test + void touchDragRotatesCamera() { + Camera camera = new Camera(640, 480); + FlyByCamera flyCam = new FlyByCamera(camera); + Vector3f initialDirection = camera.getDirection().clone(); + + flyCam.onTouch("FLYCAM_Touch", + new TouchEvent(TouchEvent.Type.MOVE, 0f, 0f, 128f, 0f), 0f); + + assertFalse(initialDirection.equals(camera.getDirection())); + } + + @Test + void touchDragRotatesCameraWhenDragToRotateIsEnabled() { + Camera camera = new Camera(640, 480); + FlyByCamera flyCam = new FlyByCamera(camera); + flyCam.setDragToRotate(true); + Vector3f initialDirection = camera.getDirection().clone(); + + flyCam.onTouch("FLYCAM_Touch", + new TouchEvent(TouchEvent.Type.MOVE, 0f, 0f, 128f, 0f), 0f); + + assertFalse(initialDirection.equals(camera.getDirection())); + } + + @Test + void disabledFlyByCameraIgnoresTouchDrag() { + Camera camera = new Camera(640, 480); + FlyByCamera flyCam = new FlyByCamera(camera); + flyCam.setEnabled(false); + Vector3f initialDirection = camera.getDirection().clone(); + + flyCam.onTouch("FLYCAM_Touch", + new TouchEvent(TouchEvent.Type.MOVE, 0f, 0f, 128f, 0f), 0f); + + assertEquals(initialDirection, camera.getDirection()); + } +}