1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2
3 #include <boost/cstdint.hpp>
4
5 #include <SDL_mouse.h>
6 #include <SDL_keycode.h>
7
8 #include "OrbitController.h"
9 #include "Game/Camera.h"
10 #include "Game/UI/MouseHandler.h"
11 #include "Map/Ground.h"
12 #include "System/Log/ILog.h"
13 #include "System/Config/ConfigHandler.h"
14 #include "System/Input/KeyInput.h"
15
16 CONFIG(bool, OrbitControllerEnabled).defaultValue(true);
17 CONFIG(float, OrbitControllerOrbitSpeed).defaultValue(0.25f).minimumValue(0.1f).maximumValue(10.0f);
18 CONFIG(float, OrbitControllerPanSpeed).defaultValue(2.00f).minimumValue(0.1f).maximumValue(10.0f);
19 CONFIG(float, OrbitControllerZoomSpeed).defaultValue(5.00f).minimumValue(0.1f).maximumValue(10.0f);
20
21 #define DEG2RAD(a) ((a) * (3.141592653f / 180.0f))
22 #define RAD2DEG(a) ((a) * (180.0f / 3.141592653f))
23
COrbitController()24 COrbitController::COrbitController():
25 lastMouseMoveX(0), lastMouseMoveY(0),
26 lastMousePressX(0), lastMousePressY(0),
27 lastMouseButton(-1),
28 currentState(Orbiting),
29 distance(512.0f), cDistance(512.0f),
30 rotation(0.0f), cRotation(0.0f),
31 elevation(0.0f), cElevation(0.0f)
32 {
33 enabled = configHandler->GetBool("OrbitControllerEnabled");
34
35 orbitSpeedFact = configHandler->GetFloat("OrbitControllerOrbitSpeed");
36 panSpeedFact = configHandler->GetFloat("OrbitControllerPanSpeed");
37 zoomSpeedFact = configHandler->GetFloat("OrbitControllerZoomSpeed");
38 }
39
Init(const float3 & p,const float3 & tar)40 void COrbitController::Init(const float3& p, const float3& tar)
41 {
42 const float l = (tar == ZeroVector)?
43 std::max(CGround::LineGroundCol(p, p + camera->forward * 1024.0f, false), 512.0f):
44 p.distance(tar);
45
46 const float3 t = (tar == ZeroVector)? (p + camera->forward * l): tar;
47 const float3 v = (t - p);
48 const float3 w = (v / v.Length()); // do not normalize v in-place
49
50 const float d = v.Length();
51 const float e = RAD2DEG(math::acos(v.Length2D() / d));
52 const float r = RAD2DEG(math::acos(w.x));
53
54 distance = cDistance = d;
55 elevation = cElevation = e;
56 rotation = cRotation = (v.z > 0.0f)? 180.0f + r: 180.0f - r;
57 cen = t;
58 }
59
60
61
Update()62 void COrbitController::Update()
63 {
64 if (!KeyInput::GetKeyModState(KMOD_GUI)) {
65 return;
66 }
67
68 const int x = mouse->lastx;
69 const int y = mouse->lasty;
70
71 const int pdx = lastMousePressX - x;
72 const int pdy = lastMousePressY - y;
73 const int rdx = lastMouseMoveX - x;
74 const int rdy = lastMouseMoveY - y;
75
76 lastMouseMoveX = x;
77 lastMouseMoveY = y;
78
79 MyMouseMove(pdx, pdy, rdx, rdy, lastMouseButton);
80 }
81
82
83
MousePress(int x,int y,int button)84 void COrbitController::MousePress(int x, int y, int button)
85 {
86 lastMousePressX = x;
87 lastMousePressY = y;
88 lastMouseButton = button;
89 cDistance = distance;
90 cRotation = rotation;
91 cElevation = elevation;
92
93 switch (button) {
94 case SDL_BUTTON_LEFT: { currentState = Orbiting; } break;
95 case SDL_BUTTON_MIDDLE: { currentState = Panning; } break;
96 case SDL_BUTTON_RIGHT: { currentState = Zooming; } break;
97 }
98 }
99
MouseRelease(int x,int y,int button)100 void COrbitController::MouseRelease(int x, int y, int button)
101 {
102 lastMousePressX = x;
103 lastMousePressY = y;
104 lastMouseButton = -1;
105 currentState = None;
106 }
107
108
MouseMove(float3 move)109 void COrbitController::MouseMove(float3 move)
110 {
111 // only triggers on SDL_BUTTON_MIDDLE (see CMouseHandler::MouseMove())
112 }
113
MyMouseMove(int dx,int dy,int rdx,int rdy,int button)114 void COrbitController::MyMouseMove(int dx, int dy, int rdx, int rdy, int button)
115 {
116 switch (button) {
117 case SDL_BUTTON_LEFT: {
118 rotation = cRotation - (dx * orbitSpeedFact);
119 elevation = cElevation - (dy * orbitSpeedFact);
120 } break;
121
122 case SDL_BUTTON_RIGHT: {
123 distance = cDistance - (dy * zoomSpeedFact);
124 } break;
125 }
126
127 if (elevation > 89.0f) elevation = 89.0f;
128 if (elevation < -89.0f) elevation = -89.0f;
129 if (distance < 1.0f) distance = 1.0f;
130
131 switch (button) {
132 case SDL_BUTTON_LEFT: {
133 Orbit();
134 } break;
135
136 case SDL_BUTTON_MIDDLE: {
137 Pan(rdx, rdy);
138 } break;
139
140 case SDL_BUTTON_RIGHT: {
141 Zoom();
142 } break;
143 }
144 }
145
GetPos() const146 float3 COrbitController::GetPos() const
147 {
148 return camera->GetPos();
149 }
150
GetDir() const151 float3 COrbitController::GetDir() const
152 {
153 float3 dir = cen - camera->GetPos();
154 dir.ANormalize();
155 return dir;
156 }
157
Orbit()158 void COrbitController::Orbit()
159 {
160 camera->SetPos(cen + GetOrbitPos());
161 camera->SetPos((camera->GetPos() + XZVector) + (UpVector * std::max(camera->GetPos().y, CGround::GetHeightReal(camera->GetPos().x, camera->GetPos().z, false))));
162 camera->forward = (cen - camera->GetPos()).ANormalize();
163 camera->up = UpVector;
164 }
165
Pan(int rdx,int rdy)166 void COrbitController::Pan(int rdx, int rdy)
167 {
168 // horizontal pan
169 camera->SetPos(camera->GetPos() + (camera->right * -rdx * panSpeedFact));
170 cen += (camera->right * -rdx * panSpeedFact);
171
172 // vertical pan
173 camera->SetPos(camera->GetPos() + (camera->up * rdy * panSpeedFact));
174 cen += (camera->up * rdy * panSpeedFact);
175
176
177 // don't allow orbit center or ourselves to drop below the terrain
178 const float camGH = CGround::GetHeightReal(camera->GetPos().x, camera->GetPos().z, false);
179 const float cenGH = CGround::GetHeightReal(cen.x, cen.z, false);
180
181 if (camera->GetPos().y < camGH) {
182 camera->SetPos((camera->GetPos() * XZVector) + (UpVector * camGH));
183 }
184
185 if (cen.y < cenGH) {
186 cen.y = cenGH;
187 camera->forward = (cen - camera->GetPos()).ANormalize();
188
189 Init(camera->GetPos(), cen);
190 }
191 }
192
Zoom()193 void COrbitController::Zoom()
194 {
195 camera->SetPos(cen - (camera->forward * distance));
196 }
197
198
199
KeyMove(float3 move)200 void COrbitController::KeyMove(float3 move)
201 {
202 // higher framerate means we take smaller steps per frame
203 // (x and y are lastFrameTime in secs., 200FPS ==> 0.005s)
204 Pan(int(move.x * -1000), int(move.y * 1000));
205 }
206
ScreenEdgeMove(float3 move)207 void COrbitController::ScreenEdgeMove(float3 move)
208 {
209 Pan(int(move.x * -1000), int(move.y * 1000));
210 }
211
MouseWheelMove(float move)212 void COrbitController::MouseWheelMove(float move)
213 {
214 }
215
SetPos(const float3 & newPos)216 void COrbitController::SetPos(const float3& newPos)
217 {
218 if (KeyInput::GetKeyModState(KMOD_GUI)) {
219 return;
220 }
221
222 // support minimap position hopping
223 const float dx = newPos.x - camera->GetPos().x;
224 const float dz = newPos.z - camera->GetPos().z;
225
226 cen.x += dx;
227 cen.z += dz;
228 cen.y = CGround::GetHeightReal(cen.x, cen.z, false);
229
230 camera->SetPos(camera->GetPos() + float3(dx, 0.0f, dz));
231 Init(camera->GetPos(), cen);
232 }
233
GetOrbitPos() const234 float3 COrbitController::GetOrbitPos() const
235 {
236 const float beta = DEG2RAD(elevation);
237 const float gamma = DEG2RAD(rotation);
238
239 float cx = distance;
240 float cy = 0.0f;
241 float cz = 0.0f;
242 float tx;
243
244 tx = cx;
245 cx = cx * math::cos(beta) + cy * math::sin(beta);
246 cy = tx * math::sin(beta) + cy * math::cos(beta);
247
248 tx = cx;
249 cx = cx * math::cos(gamma) - cz * math::sin(gamma);
250 cz = tx * math::sin(gamma) + cz * math::cos(gamma);
251
252 return float3(cx, cy, cz);
253 }
254
255
256
SwitchFrom() const257 float3 COrbitController::SwitchFrom() const
258 {
259 return camera->GetPos();
260 }
261
SwitchTo(bool showText)262 void COrbitController::SwitchTo(bool showText)
263 {
264 if (showText) {
265 LOG("Switching to Orbit style camera");
266 }
267
268 Init(camera->GetPos(), ZeroVector);
269 }
270
271
272
GetState(StateMap & sm) const273 void COrbitController::GetState(StateMap& sm) const
274 {
275 CCameraController::GetState(sm);
276
277 sm["tx"] = cen.x;
278 sm["ty"] = cen.y;
279 sm["tz"] = cen.z;
280 }
281
SetState(const StateMap & sm)282 bool COrbitController::SetState(const StateMap& sm)
283 {
284 CCameraController::SetState(sm);
285
286 SetStateFloat(sm, "tx", cen.x);
287 SetStateFloat(sm, "ty", cen.y);
288 SetStateFloat(sm, "tz", cen.z);
289 return true;
290 }
291