Zappy GUI
C++ graphic client: renderer, camera, network
Loading...
Searching...
No Matches
CameraController.cpp
Go to the documentation of this file.
1
2
4
5#include <cmath>
6
7#include "raymath.h"
8
9static float smoothstep(float t)
10{
11 t = t < 0.0f ? 0.0f : (t > 1.0f ? 1.0f : t);
12 return t * t * (3.0f - 2.0f * t);
13}
14
15void CameraController::init(float worldWidth, float worldHeight)
16{
17 _camera = {.position = {0.0f, 10.0f, 10.0f},
18 .target = {0.0f, 0.0f, 0.0f},
19 .up = {0.0f, 1.0f, 0.0f},
20 .fovy = 45.0f,
21 .projection = CAMERA_PERSPECTIVE};
23 _applyOrbital(worldWidth, worldHeight);
24}
25
26void CameraController::_orbitalDest(float worldWidth, float worldHeight, Vector3& outPos,
27 Vector3& outTarget) const
28{
29 float maxDim = fmaxf(worldWidth, worldHeight);
30 float radius = maxDim * 1.25f;
31 float aspectX = 0.5f * (1.0f + worldWidth / maxDim);
32 float aspectZ = 0.5f * (1.0f + worldHeight / maxDim);
33 outPos.x = cosf(_angle) * radius * aspectX;
34 outPos.z = sinf(_angle) * radius * aspectZ;
35 float currentRadius = sqrtf(outPos.x * outPos.x + outPos.z * outPos.z);
36 outPos.y = _height * (currentRadius / radius);
37 outTarget = {0.0f, 0.0f, 0.0f};
38}
39
40void CameraController::_applyOrbital(float worldWidth, float worldHeight)
41{
42 float maxDim = fmaxf(worldWidth, worldHeight);
43 float radius = maxDim * 1.25f;
44 float aspectX = 0.5f * (1.0f + worldWidth / maxDim);
45 float aspectZ = 0.5f * (1.0f + worldHeight / maxDim);
46 float currentRadius;
47
48 _camera.position.x = cosf(_angle) * radius * aspectX;
49 _camera.position.z = sinf(_angle) * radius * aspectZ;
50 currentRadius =
51 sqrtf(_camera.position.x * _camera.position.x + _camera.position.z * _camera.position.z);
52 _camera.position.y = _height * (currentRadius / radius);
53 _camera.target = {0.0f, 0.0f, 0.0f};
54}
55
56bool CameraController::_followTarget(const WorldState* world, Vector3& outPos,
57 Vector3& outLookAt) const
58{
59 if (!world || !world->playerExists(_followedPlayerId)) return false;
60 const Player& p = world->players.at(_followedPlayerId);
61 Vector3 pivot = p.visual.pos;
62 float yawRad = p.visual.angle * DEG2RAD;
63 float facingX = -cosf(yawRad);
64 float facingZ = sinf(yawRad);
65 outPos = {pivot.x - facingX * followDist, pivot.y + followHeight,
66 pivot.z - facingZ * followDist};
67 outLookAt = {pivot.x, pivot.y + followLookOffsetY, pivot.z};
68 return true;
69}
70
71void CameraController::_startTransition(Vector3 toPos, Vector3 toTarget, Mode nextMode)
72{
73 _fromPos = _camera.position;
74 _fromTarget = _camera.target;
75 _toPos = toPos;
76 _toTarget = toTarget;
77 _pendingMode = nextMode;
78 _transitionT = 0.0f;
79}
80
81void CameraController::update(float dt, float worldWidth, float worldHeight,
82 const WorldState* world)
83{
84 _lastWorldWidth = worldWidth;
85 _lastWorldHeight = worldHeight;
87 _transitionT += dt;
89
90 // For follow transitions: keep _toPos/_toTarget tracking the player live
92 Vector3 livePos, liveLookAt;
93 if (_followTarget(world, livePos, liveLookAt)) {
94 _toPos = livePos;
95 _toTarget = liveLookAt;
96 }
97 }
98
99 _camera.position = Vector3Lerp(_fromPos, _toPos, alpha);
100 _camera.target = Vector3Lerp(_fromTarget, _toTarget, alpha);
101
103 _camera.position = _toPos;
104 _camera.target = _toTarget;
107 }
108 return;
109 }
110
111 switch (_mode) {
112 case Mode::Freecam: {
113 Vector3 dir = {cosf(_freecamYaw) * cosf(_freecamPitch), sinf(_freecamPitch),
114 sinf(_freecamYaw) * cosf(_freecamPitch)};
115 _camera.target = Vector3Add(_camera.position, dir);
116 break;
117 }
118
119 case Mode::Follow:
120 case Mode::FollowLive: {
121 Vector3 pos, lookAt;
122 if (_followTarget(world, pos, lookAt)) {
123 _camera.position = pos;
124 _camera.target = lookAt;
126 } else {
127 stopFollow();
128 }
129 break;
130 }
131
132 case Mode::Orbital:
133 _applyOrbital(worldWidth, worldHeight);
134 break;
135 }
136}
137
139{
140 if (isTransitioning()) return;
141
142 if (_mode == Mode::Freecam)
144 else if (_mode == Mode::Orbital)
146}
147
149{
150 float dt = GetFrameTime();
151 // KEY_A maps to 'Q' on AZERTY
152 if (IsKeyDown(KEY_A) || IsKeyDown(KEY_LEFT)) _angle += orbitalMoveSpeed * dt;
153 if (IsKeyDown(KEY_D) || IsKeyDown(KEY_RIGHT)) _angle -= orbitalMoveSpeed * dt;
154}
155
157{
158 Vector2 delta = GetMouseDelta();
159 _freecamYaw += delta.x * freecamLookSpeed;
160 _freecamPitch -= delta.y * freecamLookSpeed;
161 _freecamPitch = Clamp(_freecamPitch, -PI / 2.0f + 0.01f, PI / 2.0f - 0.01f);
162
163 Vector3 forward = {cosf(_freecamYaw) * cosf(_freecamPitch), sinf(_freecamPitch),
164 sinf(_freecamYaw) * cosf(_freecamPitch)};
165 Vector3 right = Vector3Normalize(Vector3CrossProduct(forward, {0.0f, 1.0f, 0.0f}));
166 float sp = freecamMoveSpeed * GetFrameTime();
167
168 // ZQSD on AZERTY = KEY_W/KEY_A/KEY_S/KEY_D in raylib
169 if (IsKeyDown(KEY_W))
170 _camera.position = Vector3Add(_camera.position, Vector3Scale(forward, sp));
171 if (IsKeyDown(KEY_S))
172 _camera.position = Vector3Add(_camera.position, Vector3Scale(forward, -sp));
173 if (IsKeyDown(KEY_D)) _camera.position = Vector3Add(_camera.position, Vector3Scale(right, sp));
174 if (IsKeyDown(KEY_A)) _camera.position = Vector3Add(_camera.position, Vector3Scale(right, -sp));
175 if (IsKeyDown(KEY_SPACE)) _camera.position.y += sp;
176 if (IsKeyDown(KEY_LEFT_SHIFT)) _camera.position.y -= sp;
177}
178
180{
181 if (isTransitioning() || _mode == Mode::Freecam) return;
184 Vector3 dir = Vector3Subtract(_camera.target, _camera.position);
185 _freecamYaw = atan2f(dir.z, dir.x);
186 _freecamPitch = atan2f(dir.y, sqrtf(dir.x * dir.x + dir.z * dir.z));
187 DisableCursor();
188 // Freecam starts from current pos — no fly-in, just activate
190 _transitionT = transitionDuration; // mark as not transitioning
191}
192
194{
195 if (_mode != Mode::Freecam) return;
198 EnableCursor();
199 Vector3 orbPos, orbTarget;
200 _orbitalDest(_lastWorldWidth, _lastWorldHeight, orbPos, orbTarget);
201 _startTransition(orbPos, orbTarget, Mode::Orbital);
202}
203
205{
206 if (isTransitioning()) return;
207 if (_mode == Mode::Freecam) EnableCursor();
208 _followedPlayerId = playerId;
209 // Destination computed in update() live; use current camera as start
210 _startTransition(_camera.position, _camera.target, Mode::Follow);
211}
212
214{
215 if (!isFollowActive()) return;
216 Vector3 orbPos, orbTarget;
217 _orbitalDest(_lastWorldWidth, _lastWorldHeight, orbPos, orbTarget);
218 _startTransition(orbPos, orbTarget, Mode::Orbital);
219}
static float smoothstep(float t)
Owns all camera logic: orbital, freecam, follow, and smooth transitions.
static float smoothstep(float t)
void _applyOrbital(float worldWidth, float worldHeight)
void init(float worldWidth, float worldHeight)
bool isFollowActive() const
bool isTransitioning() const
void _startTransition(Vector3 toPos, Vector3 toTarget, Mode nextMode)
void _orbitalDest(float worldWidth, float worldHeight, Vector3 &outPos, Vector3 &outTarget) const
void update(float dt, float worldWidth, float worldHeight, const WorldState *world)
bool _followTarget(const WorldState *world, Vector3 &outPos, Vector3 &outLookAt) const
void startFollow(int playerId)
Represents the state of the world in the game, including the map size, tile contents,...
bool playerExists(int id) const
Checks if a player with the given ID exists in the world.
std::unordered_map< int, Player > players