mirror of
https://github.com/isledecomp/isle-portable.git
synced 2026-05-02 10:33:57 +00:00
Camera relative movement (#15)
* Decouple 3rd person camera from player movement Arrow keys now move the player relative to the camera direction instead of using tank controls. The camera stays static unless explicitly controlled via right-click drag, mouse wheel, or touch gestures. Adds a single hook in CalculateNewPosDir that lets the extension take over movement computation. The extension manages its own velocity smoothing, computes camera-relative directions from an absolute yaw, and writes nav controller velocities via friend class access. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Clean up ThirdPersonCamera: remove dead m_orbitYaw, extract helpers Remove m_orbitYaw (always DEFAULT_ORBIT_YAW) and pass yaw directly to ComputeOrbitVectors, eliminating save/restore blocks at all call sites. Extract GetLocalYaw() and InitAbsoluteYaw() helpers to deduplicate repeated atan2 patterns. Simplify SetupCamera by passing DEFAULT_ORBIT_YAW directly. Move TURN_RATE constant to file scope. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Hide cursor while orbiting 3rd person camera with right mouse button Move SDL event handling from LegoInputManager::UpdateLastInputMethod to an exported function called at the end of SDL_AppEvent, so cursor state changes aren't overridden by isleapp's SDL_ShowCursor(). Use SDL_SetWindowRelativeMouseMode to hide the cursor and freeze its position while the right mouse button is held in 3rd person camera mode. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9145a23ffe
commit
be65af4550
@ -877,6 +877,10 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef EXTENSIONS
|
||||
Extensions::HandleMultiplayerSDLEvent(event);
|
||||
#endif
|
||||
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,10 @@
|
||||
|
||||
struct LegoLocation;
|
||||
class Vector3;
|
||||
namespace Multiplayer
|
||||
{
|
||||
class ThirdPersonCamera;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
@ -122,6 +126,8 @@ class LegoNavController : public MxCore {
|
||||
// LegoNavController::`scalar deleting destructor'
|
||||
|
||||
protected:
|
||||
friend class Multiplayer::ThirdPersonCamera;
|
||||
|
||||
float CalculateNewVel(float p_targetVel, float p_currentVel, float p_accel, float p_time);
|
||||
float CalculateNewTargetVel(int p_pos, int p_center, float p_max);
|
||||
float CalculateNewAccel(int p_pos, int p_center, float p_max, int p_min);
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
#include "3dmanager/lego3dmanager.h"
|
||||
#include "act3.h"
|
||||
#include "extensions/multiplayer.h"
|
||||
#include "infocenter.h"
|
||||
#include "legoanimationmanager.h"
|
||||
#include "legocameracontroller.h"
|
||||
@ -29,6 +30,8 @@
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
#include <vec.h>
|
||||
|
||||
using namespace Extensions;
|
||||
|
||||
DECOMP_SIZE_ASSERT(LegoNavController, 0x70)
|
||||
|
||||
// MSVC 4.20 didn't define a macro for this key
|
||||
@ -348,6 +351,11 @@ MxBool LegoNavController::CalculateNewPosDir(
|
||||
ProcessJoystickInput(rotatedY);
|
||||
}
|
||||
|
||||
if (Extension<MultiplayerExt>::Call(HandleNavOverride, this, p_curPos, p_curDir, p_newPos, p_newDir, deltaTime)
|
||||
.value_or(FALSE)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (m_useRotationalVel) {
|
||||
m_rotationalVel = CalculateNewVel(m_targetRotationalVel, m_rotationalVel, m_rotationalAccel * 40.0f, deltaTime);
|
||||
}
|
||||
|
||||
@ -813,8 +813,6 @@ void LegoInputManager::InitializeHaptics()
|
||||
|
||||
void LegoInputManager::UpdateLastInputMethod(SDL_Event* p_event)
|
||||
{
|
||||
Extension<MultiplayerExt>::Call(HandleSDLEvent, p_event);
|
||||
|
||||
switch (p_event->type) {
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
case SDL_EVENT_KEY_UP:
|
||||
|
||||
@ -10,9 +10,11 @@
|
||||
class IslePathActor;
|
||||
class LegoEntity;
|
||||
class LegoEventNotificationParam;
|
||||
class LegoNavController;
|
||||
class LegoPathActor;
|
||||
class LegoROI;
|
||||
class LegoWorld;
|
||||
class Vector3;
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
@ -73,6 +75,17 @@ class MultiplayerExt {
|
||||
// Returns TRUE if the caller should return early.
|
||||
static MxBool HandleTouchInput();
|
||||
|
||||
// Overrides nav controller movement for camera-relative 3rd person controls.
|
||||
// Returns TRUE if the hook handled movement (caller should return early).
|
||||
static MxBool HandleNavOverride(
|
||||
LegoNavController* p_nav,
|
||||
const Vector3& p_curPos,
|
||||
const Vector3& p_curDir,
|
||||
Vector3& p_newPos,
|
||||
Vector3& p_newDir,
|
||||
float p_deltaTime
|
||||
);
|
||||
|
||||
static void SetNetworkManager(Multiplayer::NetworkManager* p_networkManager);
|
||||
static Multiplayer::NetworkManager* GetNetworkManager();
|
||||
|
||||
@ -84,6 +97,7 @@ class MultiplayerExt {
|
||||
|
||||
#ifdef EXTENSIONS
|
||||
LEGO1_EXPORT bool IsMultiplayerRejected();
|
||||
LEGO1_EXPORT void HandleMultiplayerSDLEvent(SDL_Event* p_event);
|
||||
|
||||
constexpr auto HandleCreate = &MultiplayerExt::HandleCreate;
|
||||
constexpr auto HandleWorldEnable = &MultiplayerExt::HandleWorldEnable;
|
||||
@ -100,6 +114,7 @@ constexpr auto CheckRejected = &MultiplayerExt::CheckRejected;
|
||||
constexpr auto HandleSDLEvent = &MultiplayerExt::HandleSDLEvent;
|
||||
constexpr auto IsThirdPersonCameraActive = &MultiplayerExt::IsThirdPersonCameraActive;
|
||||
constexpr auto HandleTouchInput = &MultiplayerExt::HandleTouchInput;
|
||||
constexpr auto HandleNavOverride = &MultiplayerExt::HandleNavOverride;
|
||||
#else
|
||||
constexpr decltype(&MultiplayerExt::HandleCreate) HandleCreate = nullptr;
|
||||
constexpr decltype(&MultiplayerExt::HandleWorldEnable) HandleWorldEnable = nullptr;
|
||||
@ -116,6 +131,7 @@ constexpr decltype(&MultiplayerExt::CheckRejected) CheckRejected = nullptr;
|
||||
constexpr decltype(&MultiplayerExt::HandleSDLEvent) HandleSDLEvent = nullptr;
|
||||
constexpr decltype(&MultiplayerExt::IsThirdPersonCameraActive) IsThirdPersonCameraActive = nullptr;
|
||||
constexpr decltype(&MultiplayerExt::HandleTouchInput) HandleTouchInput = nullptr;
|
||||
constexpr decltype(&MultiplayerExt::HandleNavOverride) HandleNavOverride = nullptr;
|
||||
#endif
|
||||
|
||||
}; // namespace Extensions
|
||||
|
||||
@ -9,9 +9,11 @@
|
||||
#include <cstdint>
|
||||
|
||||
class IslePathActor;
|
||||
class LegoNavController;
|
||||
class LegoPathActor;
|
||||
class LegoROI;
|
||||
class LegoWorld;
|
||||
class Vector3;
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
@ -54,18 +56,31 @@ class ThirdPersonCamera {
|
||||
void OnWorldEnabled(LegoWorld* p_world);
|
||||
void OnWorldDisabled(LegoWorld* p_world);
|
||||
|
||||
// Camera-relative movement override (called from nav controller hook)
|
||||
MxBool HandleCameraRelativeMovement(
|
||||
LegoNavController* p_nav,
|
||||
const Vector3& p_curPos,
|
||||
const Vector3& p_curDir,
|
||||
Vector3& p_newPos,
|
||||
Vector3& p_newDir,
|
||||
float p_deltaTime
|
||||
);
|
||||
|
||||
// Free camera input handling
|
||||
void HandleSDLEvent(SDL_Event* p_event);
|
||||
bool IsTouchGestureActive() const { return m_touchGestureActive; }
|
||||
|
||||
private:
|
||||
// Orbit camera helpers
|
||||
void ComputeOrbitVectors(Mx3DPointFloat& p_at, Mx3DPointFloat& p_dir, Mx3DPointFloat& p_up) const;
|
||||
void ComputeOrbitVectors(float p_yaw, Mx3DPointFloat& p_at, Mx3DPointFloat& p_dir, Mx3DPointFloat& p_up) const;
|
||||
void ApplyOrbitCamera();
|
||||
void ResetOrbitState();
|
||||
void ClampPitch();
|
||||
void ClampDistance();
|
||||
|
||||
float GetLocalYaw(LegoROI* p_roi) const;
|
||||
void InitAbsoluteYaw(LegoROI* p_roi);
|
||||
|
||||
void SetupCamera(LegoPathActor* p_actor);
|
||||
void ReinitForCharacter();
|
||||
|
||||
@ -93,9 +108,10 @@ class ThirdPersonCamera {
|
||||
bool m_showNameBubble;
|
||||
|
||||
// Orbit camera state
|
||||
float m_orbitYaw;
|
||||
float m_orbitPitch;
|
||||
float m_orbitDistance;
|
||||
float m_absoluteYaw; // Camera yaw in world space (decoupled from player facing)
|
||||
float m_smoothedSpeed; // Extension-managed velocity for smooth acceleration/deceleration
|
||||
|
||||
// Touch gesture tracking
|
||||
bool m_touchGestureActive = false;
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
#include "legoeventnotificationparam.h"
|
||||
#include "legogamestate.h"
|
||||
#include "legoinputmanager.h"
|
||||
#include "legonavcontroller.h"
|
||||
#include "legopathactor.h"
|
||||
#include "misc.h"
|
||||
#include "roi/legoroi.h"
|
||||
@ -323,6 +324,27 @@ MxBool MultiplayerExt::HandleTouchInput()
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
MxBool MultiplayerExt::HandleNavOverride(
|
||||
LegoNavController* p_nav,
|
||||
const Vector3& p_curPos,
|
||||
const Vector3& p_curDir,
|
||||
Vector3& p_newPos,
|
||||
Vector3& p_newDir,
|
||||
float p_deltaTime
|
||||
)
|
||||
{
|
||||
if (!s_networkManager) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
Multiplayer::ThirdPersonCamera& cam = s_networkManager->GetThirdPersonCamera();
|
||||
if (!cam.IsActive()) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return cam.HandleCameraRelativeMovement(p_nav, p_curPos, p_curDir, p_newPos, p_newDir, p_deltaTime);
|
||||
}
|
||||
|
||||
MxBool MultiplayerExt::CheckRejected()
|
||||
{
|
||||
if (s_networkManager && s_networkManager->WasRejected()) {
|
||||
@ -346,3 +368,8 @@ bool Extensions::IsMultiplayerRejected()
|
||||
{
|
||||
return Extension<MultiplayerExt>::Call(CheckRejected).value_or(FALSE);
|
||||
}
|
||||
|
||||
void Extensions::HandleMultiplayerSDLEvent(SDL_Event* p_event)
|
||||
{
|
||||
Extension<MultiplayerExt>::Call(HandleSDLEvent, p_event);
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include "islepathactor.h"
|
||||
#include "legocameracontroller.h"
|
||||
#include "legocharactermanager.h"
|
||||
#include "legoinputmanager.h"
|
||||
#include "legonavcontroller.h"
|
||||
#include "legopathactor.h"
|
||||
#include "legovideomanager.h"
|
||||
@ -23,6 +24,8 @@
|
||||
|
||||
using namespace Multiplayer;
|
||||
|
||||
static constexpr float TURN_RATE = 10.0f;
|
||||
|
||||
// Flip a matrix from forward-z to backward-z (or vice versa) in place.
|
||||
// Same operation as IslePathActor::TurnAround: negate z, recompute right.
|
||||
static void FlipMatrixDirection(MxMatrix& p_mat)
|
||||
@ -38,8 +41,8 @@ ThirdPersonCamera::ThirdPersonCamera()
|
||||
: m_enabled(false), m_active(false), m_pendingWorldTransition(false), m_playerROI(nullptr),
|
||||
m_displayActorIndex(DISPLAY_ACTOR_NONE), m_displayROI(nullptr),
|
||||
m_animator(CharacterAnimatorConfig{/*.saveEmoteTransform=*/true}), m_showNameBubble(true),
|
||||
m_orbitYaw(DEFAULT_ORBIT_YAW), m_orbitPitch(DEFAULT_ORBIT_PITCH), m_orbitDistance(DEFAULT_ORBIT_DISTANCE),
|
||||
m_touch{}
|
||||
m_orbitPitch(DEFAULT_ORBIT_PITCH), m_orbitDistance(DEFAULT_ORBIT_DISTANCE),
|
||||
m_absoluteYaw(DEFAULT_ORBIT_YAW), m_smoothedSpeed(0.0f), m_touch{}
|
||||
{
|
||||
SDL_memset(m_displayUniqueName, 0, sizeof(m_displayUniqueName));
|
||||
}
|
||||
@ -212,9 +215,14 @@ void ThirdPersonCamera::Tick(float p_deltaTime)
|
||||
|
||||
// After a world transition, PlaceActor has now run and set the ROI to
|
||||
// the correct position. Clear the flag so subsequent OnActorEnter calls
|
||||
// work normally. ApplyOrbitCamera below handles the camera setup.
|
||||
// work normally. Initialize absolute yaw from the player's actual
|
||||
// direction so the camera starts behind the character.
|
||||
if (m_pendingWorldTransition) {
|
||||
m_pendingWorldTransition = false;
|
||||
LegoPathActor* actor = UserActor();
|
||||
if (actor && actor->GetROI()) {
|
||||
InitAbsoluteYaw(actor->GetROI());
|
||||
}
|
||||
}
|
||||
|
||||
// While a cam anim locks the player (actor state c_disabled), calling
|
||||
@ -393,6 +401,22 @@ void ThirdPersonCamera::OnWorldDisabled(LegoWorld* p_world)
|
||||
m_animator.ClearAll();
|
||||
}
|
||||
|
||||
float ThirdPersonCamera::GetLocalYaw(LegoROI* p_roi) const
|
||||
{
|
||||
if (p_roi) {
|
||||
const float* dir = p_roi->GetWorldDirection();
|
||||
float playerWorldYaw = SDL_atan2f(-dir[0], dir[2]);
|
||||
return m_absoluteYaw - playerWorldYaw;
|
||||
}
|
||||
return m_absoluteYaw;
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::InitAbsoluteYaw(LegoROI* p_roi)
|
||||
{
|
||||
const float* dir = p_roi->GetWorldDirection();
|
||||
m_absoluteYaw = SDL_atan2f(-dir[0], dir[2]) + DEFAULT_ORBIT_YAW;
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::SetupCamera(LegoPathActor* p_actor)
|
||||
{
|
||||
LegoWorld* world = CurrentWorld();
|
||||
@ -400,9 +424,18 @@ void ThirdPersonCamera::SetupCamera(LegoPathActor* p_actor)
|
||||
return;
|
||||
}
|
||||
|
||||
Mx3DPointFloat at, dir, up;
|
||||
ComputeOrbitVectors(at, dir, up);
|
||||
world->GetCameraController()->SetWorldTransform(at, dir, up);
|
||||
LegoROI* roi = p_actor->GetROI();
|
||||
if (roi) {
|
||||
InitAbsoluteYaw(roi);
|
||||
}
|
||||
m_smoothedSpeed = 0.0f;
|
||||
|
||||
// InitAbsoluteYaw sets m_absoluteYaw = playerYaw + DEFAULT_ORBIT_YAW,
|
||||
// so localYaw = m_absoluteYaw - playerYaw = DEFAULT_ORBIT_YAW.
|
||||
Mx3DPointFloat at, camDir, up;
|
||||
ComputeOrbitVectors(DEFAULT_ORBIT_YAW, at, camDir, up);
|
||||
|
||||
world->GetCameraController()->SetWorldTransform(at, camDir, up);
|
||||
p_actor->TransformPointOfView();
|
||||
}
|
||||
|
||||
@ -489,15 +522,20 @@ void ThirdPersonCamera::SetNameBubbleVisible(bool p_visible)
|
||||
m_animator.SetNameBubbleVisible(p_visible);
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::ComputeOrbitVectors(Mx3DPointFloat& p_at, Mx3DPointFloat& p_dir, Mx3DPointFloat& p_up) const
|
||||
void ThirdPersonCamera::ComputeOrbitVectors(
|
||||
float p_yaw,
|
||||
Mx3DPointFloat& p_at,
|
||||
Mx3DPointFloat& p_dir,
|
||||
Mx3DPointFloat& p_up
|
||||
) const
|
||||
{
|
||||
// Convert spherical coordinates to camera offset in entity-local space.
|
||||
// The ROI uses forward-z (Z+ = visual forward). The camera orbits
|
||||
// behind the character, so at yaw=0 it sits at local -Z.
|
||||
float cosP = SDL_cosf(m_orbitPitch);
|
||||
float sinP = SDL_sinf(m_orbitPitch);
|
||||
float sinY = SDL_sinf(m_orbitYaw);
|
||||
float cosY = SDL_cosf(m_orbitYaw);
|
||||
float sinY = SDL_sinf(p_yaw);
|
||||
float cosY = SDL_cosf(p_yaw);
|
||||
|
||||
p_at = Mx3DPointFloat(
|
||||
m_orbitDistance * sinY * cosP,
|
||||
@ -519,17 +557,23 @@ void ThirdPersonCamera::ApplyOrbitCamera()
|
||||
return;
|
||||
}
|
||||
|
||||
Mx3DPointFloat at, dir, up;
|
||||
ComputeOrbitVectors(at, dir, up);
|
||||
world->GetCameraController()->SetWorldTransform(at, dir, up);
|
||||
// Derive entity-local yaw from absolute yaw and player's world facing.
|
||||
// This prevents the camera from rotating when the player turns.
|
||||
float localYaw = GetLocalYaw(actor->GetROI());
|
||||
|
||||
Mx3DPointFloat at, camDir, up;
|
||||
ComputeOrbitVectors(localYaw, at, camDir, up);
|
||||
|
||||
world->GetCameraController()->SetWorldTransform(at, camDir, up);
|
||||
actor->TransformPointOfView();
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::ResetOrbitState()
|
||||
{
|
||||
m_orbitYaw = DEFAULT_ORBIT_YAW;
|
||||
m_orbitPitch = DEFAULT_ORBIT_PITCH;
|
||||
m_orbitDistance = DEFAULT_ORBIT_DISTANCE;
|
||||
m_absoluteYaw = DEFAULT_ORBIT_YAW;
|
||||
m_smoothedSpeed = 0.0f;
|
||||
m_touch = {};
|
||||
}
|
||||
|
||||
@ -553,6 +597,146 @@ void ThirdPersonCamera::ClampDistance()
|
||||
}
|
||||
}
|
||||
|
||||
MxBool ThirdPersonCamera::HandleCameraRelativeMovement(
|
||||
LegoNavController* p_nav,
|
||||
const Vector3& p_curPos,
|
||||
const Vector3& p_curDir,
|
||||
Vector3& p_newPos,
|
||||
Vector3& p_newDir,
|
||||
float p_deltaTime
|
||||
)
|
||||
{
|
||||
// Read keyboard state
|
||||
LegoInputManager* inputManager = InputManager();
|
||||
MxU32 keyFlags = 0;
|
||||
if (!inputManager || inputManager->GetNavigationKeyStates(keyFlags) == FAILURE) {
|
||||
keyFlags = 0;
|
||||
}
|
||||
|
||||
// Compute camera world-forward and right from absolute yaw
|
||||
float camForwardX = -SDL_sinf(m_absoluteYaw);
|
||||
float camForwardZ = SDL_cosf(m_absoluteYaw);
|
||||
float camRightX = SDL_cosf(m_absoluteYaw);
|
||||
float camRightZ = SDL_sinf(m_absoluteYaw);
|
||||
|
||||
// Map key flags to combined movement direction
|
||||
float moveDirX = 0.0f;
|
||||
float moveDirZ = 0.0f;
|
||||
|
||||
if (keyFlags & LegoInputManager::c_up) {
|
||||
moveDirX += camForwardX;
|
||||
moveDirZ += camForwardZ;
|
||||
}
|
||||
if (keyFlags & LegoInputManager::c_down) {
|
||||
moveDirX -= camForwardX;
|
||||
moveDirZ -= camForwardZ;
|
||||
}
|
||||
if (keyFlags & LegoInputManager::c_left) {
|
||||
moveDirX -= camRightX;
|
||||
moveDirZ -= camRightZ;
|
||||
}
|
||||
if (keyFlags & LegoInputManager::c_right) {
|
||||
moveDirX += camRightX;
|
||||
moveDirZ += camRightZ;
|
||||
}
|
||||
|
||||
// Normalize movement direction
|
||||
float moveDirLen = SDL_sqrtf(moveDirX * moveDirX + moveDirZ * moveDirZ);
|
||||
bool hasInput = moveDirLen > 0.001f;
|
||||
if (hasInput) {
|
||||
moveDirX /= moveDirLen;
|
||||
moveDirZ /= moveDirLen;
|
||||
}
|
||||
|
||||
// Smooth speed using acceleration/deceleration (mirroring nav controller's model)
|
||||
float maxSpeed = p_nav->m_maxLinearVel;
|
||||
if (hasInput) {
|
||||
float accel = p_nav->m_maxLinearAccel;
|
||||
m_smoothedSpeed += accel * p_deltaTime;
|
||||
if (m_smoothedSpeed > maxSpeed) {
|
||||
m_smoothedSpeed = maxSpeed;
|
||||
}
|
||||
}
|
||||
else {
|
||||
float decel = p_nav->m_maxLinearDeccel;
|
||||
m_smoothedSpeed -= decel * p_deltaTime;
|
||||
if (m_smoothedSpeed < 0.0f) {
|
||||
m_smoothedSpeed = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_smoothedSpeed < p_nav->m_zeroThreshold && !hasInput) {
|
||||
m_smoothedSpeed = 0.0f;
|
||||
// No movement, keep current position and direction
|
||||
p_newPos = p_curPos;
|
||||
p_newDir = p_curDir;
|
||||
}
|
||||
else {
|
||||
// Compute new position. Include p_curDir[1] (slope from boundary
|
||||
// orientation) so the actor follows terrain height changes.
|
||||
float speed = m_smoothedSpeed * p_deltaTime;
|
||||
if (hasInput) {
|
||||
p_newPos[0] = p_curPos[0] + moveDirX * speed;
|
||||
p_newPos[1] = p_curPos[1] + p_curDir[1] * speed;
|
||||
p_newPos[2] = p_curPos[2] + moveDirZ * speed;
|
||||
|
||||
// Smooth turn: interpolate facing toward movement direction
|
||||
float targetYaw = SDL_atan2f(-moveDirX, moveDirZ);
|
||||
float currentYaw = SDL_atan2f(-p_curDir[0], p_curDir[2]);
|
||||
float angleDiff = targetYaw - currentYaw;
|
||||
|
||||
// Wrap to [-PI, PI]
|
||||
while (angleDiff > SDL_PI_F) {
|
||||
angleDiff -= 2.0f * SDL_PI_F;
|
||||
}
|
||||
while (angleDiff < -SDL_PI_F) {
|
||||
angleDiff += 2.0f * SDL_PI_F;
|
||||
}
|
||||
|
||||
float maxTurn = TURN_RATE * p_deltaTime;
|
||||
if (SDL_fabsf(angleDiff) > maxTurn) {
|
||||
angleDiff = angleDiff > 0 ? maxTurn : -maxTurn;
|
||||
}
|
||||
|
||||
float newYaw = currentYaw + angleDiff;
|
||||
p_newDir[0] = -SDL_sinf(newYaw);
|
||||
p_newDir[1] = p_curDir[1];
|
||||
p_newDir[2] = SDL_cosf(newYaw);
|
||||
}
|
||||
else {
|
||||
// Decelerating: continue in current direction
|
||||
p_newPos[0] = p_curPos[0] + p_curDir[0] * speed;
|
||||
p_newPos[1] = p_curPos[1] + p_curDir[1] * speed;
|
||||
p_newPos[2] = p_curPos[2] + p_curDir[2] * speed;
|
||||
p_newDir = p_curDir;
|
||||
}
|
||||
}
|
||||
|
||||
// Set nav controller velocities via friend access so GetWorldSpeed()
|
||||
// reports correctly for animations/network
|
||||
p_nav->m_linearVel = m_smoothedSpeed;
|
||||
// Suppress camera roll in Animate()
|
||||
p_nav->m_rotationalVel = 0.0f;
|
||||
|
||||
// Pre-set camera controller's local transform for the NEW player direction.
|
||||
// TransformPointOfView() runs after this hook returns but before Tick()'s
|
||||
// ApplyOrbitCamera(). Without this, the stale local transform (computed for
|
||||
// the old facing) composes with the new actor transform, causing a one-frame
|
||||
// camera flash in the wrong direction.
|
||||
LegoWorld* world = CurrentWorld();
|
||||
if (world && world->GetCameraController()) {
|
||||
float newPlayerYaw = SDL_atan2f(-p_newDir[0], p_newDir[2]);
|
||||
float localYaw = m_absoluteYaw - newPlayerYaw;
|
||||
|
||||
Mx3DPointFloat at, camDir, camUp;
|
||||
ComputeOrbitVectors(localYaw, at, camDir, camUp);
|
||||
|
||||
world->GetCameraController()->SetWorldTransform(at, camDir, camUp);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::HandleSDLEvent(SDL_Event* p_event)
|
||||
{
|
||||
switch (p_event->type) {
|
||||
@ -563,12 +747,21 @@ void ThirdPersonCamera::HandleSDLEvent(SDL_Event* p_event)
|
||||
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
if (p_event->motion.state & SDL_BUTTON_RMASK) {
|
||||
m_orbitYaw -= p_event->motion.xrel * 0.005f;
|
||||
m_absoluteYaw -= p_event->motion.xrel * 0.005f;
|
||||
m_orbitPitch += p_event->motion.yrel * 0.005f;
|
||||
ClampPitch();
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP: {
|
||||
SDL_Window* window = SDL_GetWindowFromID(p_event->button.windowID);
|
||||
if (window) {
|
||||
SDL_SetWindowRelativeMouseMode(window, SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_RMASK);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_EVENT_FINGER_DOWN: {
|
||||
if (m_touch.count < 2) {
|
||||
int idx = m_touch.count;
|
||||
@ -621,7 +814,7 @@ void ThirdPersonCamera::HandleSDLEvent(SDL_Event* p_event)
|
||||
// Two-finger drag for orbit
|
||||
float moveX = m_touch.x[idx] - oldX;
|
||||
float moveY = m_touch.y[idx] - oldY;
|
||||
m_orbitYaw += moveX * 2.0f;
|
||||
m_absoluteYaw += moveX * 2.0f;
|
||||
m_orbitPitch += moveY * 2.0f;
|
||||
ClampPitch();
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user