Skip to content

Commit 1c9410e

Browse files
committed
make navigation in Android easier. Physically rotate phone to rotate camera, long touch to reset camera. Swipe to translate camera.
1 parent 31dd193 commit 1c9410e

7 files changed

Lines changed: 303 additions & 53 deletions

File tree

attachments/simple_engine/engine.cpp

Lines changed: 159 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "engine.h"
1818
#include "mesh_component.h"
1919
#include "scene_loading.h"
20+
#include <cmath>
2021

2122
#include <algorithm>
2223
#include <chrono>
@@ -389,6 +390,10 @@ void Engine::handleMouseInput(float x, float y, uint32_t buttons) {
389390
// Finger just went down
390391
cameraControl.mouseLeftPressed = true;
391392
cameraControl.firstMouse = true;
393+
cameraControl.touchTotalDistance = 0.0f;
394+
cameraControl.touchDownX = x;
395+
cameraControl.touchDownY = y;
396+
cameraControl.touchStartTime = 0.0; // We'll increment this in Update
392397
}
393398

394399
if (cameraControl.firstMouse) {
@@ -399,8 +404,20 @@ void Engine::handleMouseInput(float x, float y, uint32_t buttons) {
399404

400405
// Accumulate movement deltas. These will be applied in UpdateCameraControls
401406
// AFTER ImGui has updated its capture state (post-NewFrame).
402-
cameraControl.pendingXOffset += (x - cameraControl.lastMouseX);
403-
cameraControl.pendingYOffset += (y - cameraControl.lastMouseY);
407+
float dx = (x - cameraControl.lastMouseX);
408+
float dy = (y - cameraControl.lastMouseY);
409+
cameraControl.pendingXOffset += dx;
410+
cameraControl.pendingYOffset += dy;
411+
cameraControl.touchTotalDistance += std::sqrt(dx*dx + dy*dy);
412+
413+
#if defined(PLATFORM_ANDROID)
414+
// On Android, we map SWIPE to MOVEMENT (Forward/Backward, Left/Right)
415+
// if the touch didn't start on UI.
416+
if (!cameraControl.isFirstFrameOfInteraction && !cameraControl.startedOnImGui) {
417+
cameraControl.touchMoveX = dx;
418+
cameraControl.touchMoveY = dy;
419+
}
420+
#endif
404421

405422
cameraControl.lastMouseX = x;
406423
cameraControl.lastMouseY = y;
@@ -457,15 +474,15 @@ void Engine::handleKeyInput(uint32_t key, bool pressed) {
457474
default:
458475
break;
459476
}
460-
461-
if (imguiSystem) {
462-
imguiSystem->HandleKeyboard(key, pressed);
463-
}
464477
#else
465478
// Android uses different input handling via touch events
466479
(void) key;
467480
(void) pressed;
468481
#endif
482+
483+
if (imguiSystem) {
484+
imguiSystem->HandleKeyboard(key, pressed);
485+
}
469486
}
470487

471488
void Engine::Update(TimeDelta deltaTime) {
@@ -659,6 +676,110 @@ void Engine::UpdateCameraControls(TimeDelta deltaTime) {
659676
// Check if ImGui wants to capture mouse input (updated in NewFrame)
660677
bool imguiWantsMouse = imguiSystem && imguiSystem->WantCaptureMouse();
661678

679+
#if defined(PLATFORM_ANDROID)
680+
// --- Android: Ambitious Controls ---
681+
// 1. Accelerometer -> Rotation (Tilting)
682+
float ax, ay, az;
683+
float androidPitchOffset = 0.0f;
684+
float androidYawOffset = 0.0f;
685+
if (platform->GetAccelerometerData(&ax, &ay, &az)) {
686+
// Correct for display rotation (Portrait vs Landscape vs Reversed)
687+
ax = -ax;
688+
ay = -ay;
689+
float rawX = ax;
690+
float rawY = ay;
691+
int rotation = platform->GetDisplayRotation();
692+
switch (rotation) {
693+
case 1: // ROTATION_90 (Landscape Left)
694+
ax = -rawY; ay = rawX; break;
695+
case 2: // ROTATION_180 (Portrait Upside Down)
696+
ax = -rawX; ay = -rawY; break;
697+
case 3: // ROTATION_270 (Landscape Right)
698+
ax = rawY; ay = -rawX; break;
699+
default: // ROTATION_0 (Portrait)
700+
break;
701+
}
702+
703+
// If not calibrated, take current values as neutral
704+
if (!cameraControl.tiltCalibrated) {
705+
cameraControl.tiltCenterX = ax;
706+
cameraControl.tiltCenterY = ay;
707+
cameraControl.tiltCalibrated = true;
708+
}
709+
710+
float dax = ax - cameraControl.tiltCenterX;
711+
float day = ay - cameraControl.tiltCenterY;
712+
713+
// Auto-recalibration: If the phone is held steady (small delta from current center),
714+
// we slowly drift the center point towards the current reading.
715+
// This allows the "rest" position to adapt to the user's hands.
716+
float distFromCenter = std::sqrt(dax*dax + day*day);
717+
float dt = deltaTime.count() * 0.001f;
718+
719+
if (distFromCenter < 1.5f) {
720+
// Steady detection: If we're close to the center for a while, snap it.
721+
cameraControl.tiltSteadyTime += dt;
722+
if (cameraControl.tiltSteadyTime > 0.5f) {
723+
// Drift the center towards current value to "establish a new deadzone"
724+
float driftRate = 2.0f * dt;
725+
cameraControl.tiltCenterX += dax * driftRate;
726+
cameraControl.tiltCenterY += day * driftRate;
727+
}
728+
} else {
729+
cameraControl.tiltSteadyTime = 0.0f;
730+
}
731+
732+
// Deadzone and immediate stop: if within deadzone, motion is ZERO.
733+
// Increased deadzone to 0.8f for more stability.
734+
if (std::abs(dax) < 0.8f) dax = 0.0f;
735+
if (std::abs(day) < 0.8f) day = 0.0f;
736+
737+
// We multiply by deltaTime to ensure consistent rotation speed across different frame rates.
738+
const float tiltSensitivity = 20.0f; // Degrees per second at max tilt
739+
androidYawOffset = dax * tiltSensitivity * dt;
740+
androidPitchOffset = -day * tiltSensitivity * dt;
741+
}
742+
743+
// 2. Swipe -> Movement
744+
float androidMoveForward = 0.0f;
745+
float androidMoveRight = 0.0f;
746+
if (cameraControl.mouseLeftPressed && !cameraControl.startedOnImGui) {
747+
const float moveSensitivity = 0.15f;
748+
androidMoveRight = cameraControl.touchMoveX * moveSensitivity;
749+
androidMoveForward = -cameraControl.touchMoveY * moveSensitivity;
750+
}
751+
// Clear touch movement frame delta
752+
cameraControl.touchMoveX = 0.0f;
753+
cameraControl.touchMoveY = 0.0f;
754+
755+
// 3. Tap and Hold -> Reset Camera & Recalibrate Tilt
756+
bool isHoldingToReset = false;
757+
if (cameraControl.mouseLeftPressed && !cameraControl.startedOnImGui) {
758+
cameraControl.touchStartTime += deltaTime.count() * 0.001f;
759+
// If held for more than 0.5s without moving more than 10 pixels
760+
if (cameraControl.touchStartTime > 0.5f && cameraControl.touchTotalDistance < 10.0f) {
761+
cameraControl.yaw = 0.0f;
762+
cameraControl.pitch = 0.0f;
763+
764+
// Force the camera to be level (horizon-aligned) during reset.
765+
// We extract the yaw from the base orientation and discard pitch/roll.
766+
glm::vec3 euler = glm::eulerAngles(cameraControl.baseOrientation);
767+
cameraControl.baseOrientation = glm::angleAxis(euler.y, glm::vec3(0.0f, 1.0f, 0.0f));
768+
769+
// Recalibrate: current physical orientation becomes the new "zero"
770+
// We use the rotation-corrected values already calculated in ax/ay above.
771+
cameraControl.tiltCenterX = ax;
772+
cameraControl.tiltCenterY = ay;
773+
774+
androidYawOffset = 0.0f;
775+
androidPitchOffset = 0.0f;
776+
isHoldingToReset = true;
777+
}
778+
} else {
779+
cameraControl.touchStartTime = 0.0f;
780+
}
781+
#endif
782+
662783
// INTERACTION LOCKING LOGIC:
663784
// If a touch began, we wait until ImGui has processed the first DOWN event (in NewFrame)
664785
// before deciding whether this drag belongs to the GUI or the 3D Scene.
@@ -672,24 +793,34 @@ void Engine::UpdateCameraControls(TimeDelta deltaTime) {
672793

673794
// Only apply rotation if the interaction started on the scene background
674795
if (!cameraControl.startedOnImGui) {
796+
#if !defined(PLATFORM_ANDROID)
675797
float xOffset = cameraControl.pendingXOffset * cameraControl.mouseSensitivity;
676798
float yOffset = cameraControl.pendingYOffset * cameraControl.mouseSensitivity;
677799

678800
cameraControl.yaw -= xOffset;
679801
cameraControl.pitch -= yOffset;
680-
681-
// Constrain pitch to avoid gimbal lock
682-
if (cameraControl.pitch > 89.0f)
683-
cameraControl.pitch = 89.0f;
684-
if (cameraControl.pitch < -89.0f)
685-
cameraControl.pitch = -89.0f;
802+
#endif
686803
}
687804
} else {
688805
// Reset locking state when finger is lifted
689806
cameraControl.isFirstFrameOfInteraction = true;
690807
cameraControl.startedOnImGui = false;
691808
}
692809

810+
#if defined(PLATFORM_ANDROID)
811+
// Apply Android tilt and swiping
812+
if (!isHoldingToReset) {
813+
cameraControl.yaw += androidYawOffset;
814+
cameraControl.pitch += androidPitchOffset;
815+
}
816+
#endif
817+
818+
// Constrain pitch to avoid gimbal lock
819+
if (cameraControl.pitch > 89.0f)
820+
cameraControl.pitch = 89.0f;
821+
if (cameraControl.pitch < -89.0f)
822+
cameraControl.pitch = -89.0f;
823+
693824
// Clear accumulated offsets after processing
694825
cameraControl.pendingXOffset = 0.0f;
695826
cameraControl.pendingYOffset = 0.0f;
@@ -745,6 +876,20 @@ void Engine::UpdateCameraControls(TimeDelta deltaTime) {
745876
position -= up * velocity;
746877
}
747878

879+
#if defined(PLATFORM_ANDROID)
880+
// Apply Android swipe-to-walk displacement
881+
// We use the same front/right vectors but apply the swipe deltas.
882+
// Note: androidMoveForward/Right are already calculated in the Android block above.
883+
position += front * androidMoveForward * cameraControl.cameraSpeed * 0.02f;
884+
position += right * androidMoveRight * cameraControl.cameraSpeed * 0.02f;
885+
#endif
886+
887+
#if defined(PLATFORM_ANDROID)
888+
// Apply Android swipe-based movement
889+
position += front * androidMoveForward;
890+
position += right * androidMoveRight;
891+
#endif
892+
748893
// Update camera position
749894
cameraTransform->SetPosition(position);
750895
// Apply rotation to the camera transform based on GLTF base orientation plus mouse deltas
@@ -998,33 +1143,12 @@ bool Engine::InitializeAndroid(android_app* app, const std::string& appName, boo
9981143

9991144
// Set mouse callback
10001145
platform->SetMouseCallback([this](float x, float y, uint32_t buttons) {
1001-
// Check if ImGui wants to capture mouse input first
1002-
bool imguiWantsMouse = imguiSystem && imguiSystem->WantCaptureMouse();
1003-
1004-
if (!imguiWantsMouse) {
1005-
// Handle mouse click for ball throwing (right mouse button)
1006-
if (buttons & 2) {
1007-
// Right mouse button (bit 1)
1008-
if (!cameraControl.mouseRightPressed) {
1009-
cameraControl.mouseRightPressed = true;
1010-
// Throw a ball on mouse click
1011-
ThrowBall(x, y);
1012-
}
1013-
} else {
1014-
cameraControl.mouseRightPressed = false;
1015-
}
1016-
}
1017-
1018-
if (imguiSystem) {
1019-
imguiSystem->HandleMouse(x, y, buttons);
1020-
}
1146+
handleMouseInput(x, y, buttons);
10211147
});
10221148

10231149
// Set keyboard callback
10241150
platform->SetKeyboardCallback([this](uint32_t key, bool pressed) {
1025-
if (imguiSystem) {
1026-
imguiSystem->HandleKeyboard(key, pressed);
1027-
}
1151+
handleKeyInput(key, pressed);
10281152
});
10291153

10301154
// Set char callback

attachments/simple_engine/engine.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,20 @@ class Engine
297297
bool isFirstFrameOfInteraction = true;
298298
float pendingXOffset = 0.0f;
299299
float pendingYOffset = 0.0f;
300+
301+
// Mobile movement state
302+
float touchMoveX = 0.0f; // Left/Right
303+
float touchMoveY = 0.0f; // Forward/Backward
304+
float tiltYaw = 0.0f;
305+
float tiltPitch = 0.0f;
306+
float tiltCenterX = 0.0f;
307+
float tiltCenterY = 0.0f;
308+
bool tiltCalibrated = false;
309+
float tiltSteadyTime = 0.0f;
310+
float touchTotalDistance = 0.0f;
311+
float touchDownX = 0.0f;
312+
float touchDownY = 0.0f;
313+
double touchStartTime = 0.0;
300314
} cameraControl;
301315

302316
// Mouse position tracking

0 commit comments

Comments
 (0)