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