1 /**
2 Copyright: Copyright (c) 2015-2017 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 
7 module voxelman.movement.plugin;
8 
9 import voxelman.log;
10 
11 import voxelman.math;
12 static import voxelman.math.utils;
13 
14 import pluginlib;
15 import voxelman.core.events;
16 
17 import voxelman.config.configmanager : ConfigOption, ConfigManager;
18 import voxelman.block.plugin;
19 import voxelman.dbg.plugin;
20 import voxelman.eventdispatcher.plugin;
21 import voxelman.graphics.plugin;
22 import voxelman.gui.plugin;
23 import voxelman.input.plugin;
24 import voxelman.input.keybindingmanager;
25 import voxelman.world.clientworld;
26 
27 import derelict.imgui.imgui;
28 import voxelman.utils.textformatter;
29 
30 
31 class MovementPlugin : IPlugin
32 {
33 	EventDispatcherPlugin evDispatcher;
34 	GraphicsPlugin graphics;
35 	GuiPlugin guiPlugin;
36 	InputPlugin input;
37 	BlockPluginClient blockPlugin;
38 	ClientWorld clientWorld;
39 	Debugger dbg;
40 
41 	ConfigOption fpsCameraSpeedOpt;
42 	ConfigOption fpsCameraBoostOpt;
43 	ConfigOption flightCameraSpeedOpt;
44 	ConfigOption flightCameraBoostOpt;
45 
46 	bool onGround;
47 	bool isFlying;
48 	bool noclip;
49 	float eyesHeight = 1.7;
50 	float height = 1.75;
51 	float radius = 0.25;
52 
53 	float friction = 0.9;
54 
55 	float jumpHeight = 1.5;
56 	float jumpTime = 0.3;
57 
58 	// calculated each tick from jumpHeight and jumpTime
59 	float gravity = 1;
60 	float jumpSpeed = 1; // ditto
61 
62 	vec3 speed = vec3(0,0,0);
63 	float maxFallSpeed = 100;
64 	float airSpeed = 2;
65 
66 	mixin IdAndSemverFrom!"voxelman.movement.plugininfo";
67 
68 	override void registerResources(IResourceManagerRegistry resmanRegistry)
69 	{
70 		auto keyBindingMan = resmanRegistry.getResourceManager!KeyBindingManager;
71 		keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_W, "key.forward"));
72 		keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_A, "key.left"));
73 		keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_S, "key.backward"));
74 		keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_D, "key.right"));
75 		keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_SPACE, "key.up"));
76 		keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_LEFT_CONTROL, "key.down"));
77 		keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_LEFT_SHIFT, "key.fast"));
78 		keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_KP_ADD, "key.speed_up", null, &speedUp));
79 		keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_KP_SUBTRACT, "key.speed_down", null, &speedDown));
80 		keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_F, "key.fly", null, &toggleFlying));
81 		keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_N, "key.noclip", null, &toggleNoclip));
82 
83 		ConfigManager config = resmanRegistry.getResourceManager!ConfigManager;
84 		fpsCameraSpeedOpt = config.registerOption!int("fps_camera_speed", 5);
85 		fpsCameraBoostOpt = config.registerOption!int("fps_camera_boost", 2);
86 		flightCameraSpeedOpt = config.registerOption!int("flight_camera_speed", 20);
87 		flightCameraBoostOpt = config.registerOption!int("flight_camera_boost", 5);
88 
89 		dbg = resmanRegistry.getResourceManager!Debugger;
90 	}
91 
92 	override void init(IPluginManager pluginman)
93 	{
94 		evDispatcher = pluginman.getPlugin!EventDispatcherPlugin;
95 		graphics = pluginman.getPlugin!GraphicsPlugin;
96 		guiPlugin = pluginman.getPlugin!GuiPlugin;
97 		input = pluginman.getPlugin!InputPlugin;
98 		clientWorld = pluginman.getPlugin!ClientWorld;
99 		blockPlugin = pluginman.getPlugin!BlockPluginClient;
100 
101 		auto debugClient = pluginman.getPlugin!DebugClient;
102 		debugClient.registerDebugGuiHandler(&showDebugSettings, SETTINGS_ORDER, "Movement");
103 
104 		evDispatcher.subscribeToEvent(&onPreUpdateEvent);
105 		evDispatcher.subscribeToEvent(&onPostUpdateEvent);
106 	}
107 
108 	void speedUp(string) {
109 		if (isFlying)
110 			flightCameraSpeedOpt.set(clamp(flightCameraSpeedOpt.get!uint + 1, 1, 200));
111 		else
112 			fpsCameraSpeedOpt.set(clamp(fpsCameraSpeedOpt.get!uint + 1, 1, 100));
113 	}
114 	void speedDown(string) {
115 		if (isFlying)
116 			flightCameraSpeedOpt.set(clamp(flightCameraSpeedOpt.get!uint - 1, 1, 200));
117 		else
118 			fpsCameraSpeedOpt.set(clamp(fpsCameraSpeedOpt.get!uint - 1, 1, 100));
119 	}
120 	void toggleFlying(string) {
121 		isFlying = !isFlying;
122 	}
123 	void toggleNoclip(string) {
124 		noclip = !noclip;
125 	}
126 
127 	void onPreUpdateEvent(ref PreUpdateEvent event)
128 	{
129 		if(guiPlugin.mouseLocked)
130 			handleMouse();
131 
132 		if (isCurrentChunkLoaded())
133 		{
134 			gravity = (2*jumpHeight/(jumpTime*jumpTime));
135 			jumpSpeed = sqrt(2*gravity*jumpHeight);
136 			float delta = clamp(event.deltaTime, 0, 2);
137 
138 			vec3 eyesDelta = vec3(0, eyesHeight, 0);
139 			vec3 legsPos = graphics.camera.position - eyesDelta;
140 
141 			vec3 targetDelta;
142 			if (isFlying)
143 				targetDelta = handleFlight(speed, delta);
144 			else
145 				targetDelta = handleWalk(speed, delta);
146 
147 			vec3 newLegsPos;
148 
149 			if (noclip)
150 				newLegsPos = legsPos + targetDelta;
151 			else
152 				newLegsPos = movePlayer(legsPos, targetDelta, delta);
153 
154 			graphics.camera.position = newLegsPos + eyesDelta;
155 			graphics.camera.isUpdated = false;
156 		}
157 	}
158 
159 	private void onPostUpdateEvent(ref PostUpdateEvent event)
160 	{
161 		dbg.setVar("On ground", onGround);
162 		dbg.setVar("Speed", speed.length);
163 		dbg.setVar("Loaded", isCurrentChunkLoaded());
164 	}
165 
166 	private void showDebugSettings()
167 	{
168 		igCheckbox("[N]oclip", &noclip);
169 		igCheckbox("[F]ly mode", &isFlying);
170 	}
171 
172 	bool isCurrentChunkLoaded()
173 	{
174 		bool inBorders = clientWorld
175 			.currentDimensionBorders
176 			.contains(clientWorld.observerPosition.ivector3);
177 		bool chunkLoaded = clientWorld
178 			.chunkManager
179 			.isChunkLoaded(clientWorld.observerPosition);
180 		return chunkLoaded || (!inBorders);
181 	}
182 
183 	// returns position delta
184 	vec3 handleWalk(ref vec3 speed, float dt)
185 	{
186 		InputState state = gatherInputs();
187 
188 		vec3 inputSpeed = vec3(0,0,0);
189 		vec3 inputAccel = vec3(0,0,0);
190 
191 		uint cameraSpeed = fpsCameraSpeedOpt.get!uint;
192 		if (state.boost)
193 			cameraSpeed *= fpsCameraBoostOpt.get!uint;
194 
195 		vec3 horInputs = vec3(state.inputs.x, 0, state.inputs.z);
196 		if (horInputs != vec3(0,0,0))
197 			horInputs.normalize();
198 
199 		vec3 cameraMovement = toCameraCoordinateSystem(horInputs);
200 
201 		if (onGround)
202 		{
203 			speed = vec3(0,0,0);
204 
205 			if (state.jump)
206 			{
207 				speed.y = jumpSpeed;
208 				onGround = false;
209 			}
210 
211 			if (dt > 0) {
212 				inputAccel.x = cameraMovement.x / dt;
213 				inputAccel.z = cameraMovement.z / dt;
214 			}
215 
216 			inputAccel *= cameraSpeed;
217 		}
218 		else
219 		{
220 			inputSpeed = cameraMovement * airSpeed;
221 		}
222 
223 		vec3 accel = vec3(0, -gravity, 0) + inputAccel;
224 
225 		vec3 airFrictionAccel = vec3(0, limitingFriction(std_abs(speed.y), accel.y, maxFallSpeed), 0);
226 
227 		vec3 fullAcceleration = airFrictionAccel + accel;
228 		speed += fullAcceleration * dt;
229 		vec3 targetDelta = (speed + inputSpeed) * dt;
230 		return targetDelta;
231 	}
232 
233 	vec3 handleFlight(ref vec3 speed, float dt)
234 	{
235 		InputState state = gatherInputs();
236 		uint cameraSpeed = flightCameraSpeedOpt.get!uint;
237 		if (state.boost)
238 			cameraSpeed *= flightCameraBoostOpt.get!uint;
239 
240 		vec3 inputs = vec3(state.inputs);
241 		if (inputs != vec3(0,0,0))
242 			inputs.normalize();
243 
244 		vec3 inputSpeed = toCameraCoordinateSystem(inputs);
245 		inputSpeed *= cameraSpeed;
246 		vec3 targetDelta = inputSpeed * dt;
247 
248 		return targetDelta;
249 	}
250 
251 	static struct InputState
252 	{
253 		ivec3 inputs = ivec3(0,0,0);
254 		bool boost;
255 		bool jump;
256 		bool hasHoriInput;
257 	}
258 
259 	InputState gatherInputs()
260 	{
261 		InputState state;
262 		if(guiPlugin.mouseLocked)
263 		{
264 			if(input.isKeyPressed("key.fast")) state.boost = true;
265 
266 			if(input.isKeyPressed("key.right"))
267 			{
268 				state.inputs.x = 1;
269 				state.hasHoriInput = true;
270 			}
271 			else if(input.isKeyPressed("key.left"))
272 			{
273 				state.inputs.x = -1;
274 				state.hasHoriInput = true;
275 			}
276 
277 			if(input.isKeyPressed("key.forward"))
278 			{
279 				state.inputs.z = 1;
280 				state.hasHoriInput = true;
281 			}
282 			else if(input.isKeyPressed("key.backward"))
283 			{
284 				state.inputs.z = -1;
285 				state.hasHoriInput = true;
286 			}
287 
288 			if(input.isKeyPressed("key.up")) {
289 				state.inputs.y = 1;
290 				state.jump = true;
291 			} else if(input.isKeyPressed("key.down")) state.inputs.y = -1;
292 		}
293 		return state;
294 	}
295 
296 	void handleMouse()
297 	{
298 		ivec2 mousePos = input.mousePosition;
299 		mousePos -= cast(ivec2)(guiPlugin.window.size) / 2;
300 		// scale, so up and left is positive, as rotation is anti-clockwise
301 		// and coordinate system is right-hand and -z if forward
302 		mousePos *= -1;
303 
304 		if(mousePos.x !=0 || mousePos.y !=0)
305 		{
306 			graphics.camera.rotate(vec2(mousePos));
307 		}
308 		input.mousePosition = cast(ivec2)(guiPlugin.window.size) / 2;
309 	}
310 
311 	vec3 toCameraCoordinateSystem(vec3 vector)
312 	{
313 		vec3 rightVec = graphics.camera.rotationQuatHor.rotate(vec3(1,0,0));
314 		vec3 targetVec = graphics.camera.rotationQuatHor.rotate(vec3(0,0,-1));
315 		return rightVec * vector.x + vec3(0,1,0) * vector.y + targetVec * vector.z;
316 	}
317 
318 	vec3 movePlayer(vec3 pos, vec3 delta, float dt)
319 	{
320 		float distance = delta.length;
321 		int num_steps = cast(int)ceil(distance * 2); // num cells moved
322 		if (num_steps == 0) return pos;
323 
324 		vec3 move_step = delta / num_steps;
325 
326 		onGround = false;
327 
328 		foreach(i; 0..num_steps) {
329 			pos += move_step;
330 			vec3 speed_mult = collide(pos, radius, height);
331 			speed *= speed_mult;
332 		}
333 
334 		return pos;
335 	}
336 
337 	vec3 collide(ref vec3 point, float rad, float height)
338 	{
339 		ivec3 cell = ivec3(floor(point.x), floor(point.y), floor(point.z));
340 		vec3 speed_mult = vec3(1, 1, 1);
341 
342 		Aabb body_aabb = Aabb(point+vec3(0, height/2, 0), vec3(rad, height/2, rad));
343 
344 		foreach(dy; -1..ceil(height+1)) {
345 		foreach(dx; [0, -1, 1]) {
346 		foreach(dz; [0, -1, 1]) {
347 		ivec3 local_cell = cell + ivec3(dx, dy, dz);
348 		if (clientWorld.isBlockSolid(local_cell))
349 		{
350 			Aabb cell_aabb = Aabb(vec3(local_cell) + vec3(0.5,0.5,0.5), vec3(0.5,0.5,0.5));
351 			bool collides = cell_aabb.collides(body_aabb);
352 			if (collides)
353 			{
354 				vec3 vector = cell_aabb.intersectionSize(body_aabb);
355 				int min_component;
356 				if (vector.x < vector.y) {
357 					if (vector.x < vector.z) min_component = 0;
358 					else min_component = 2;
359 				} else {
360 					if (vector.y < vector.z) min_component = 1;
361 					else min_component = 2;
362 				}
363 
364 				if (min_component == 0) // x
365 				{
366 					int dir = cell_aabb.pos.x < body_aabb.pos.x ? 1 : -1;
367 					body_aabb.pos.x = body_aabb.pos.x + vector.x * dir;
368 					speed_mult.x = 0;
369 				}
370 				else if (min_component == 1) // y
371 				{
372 					int dir = cell_aabb.pos.y < body_aabb.pos.y ? 1 : -1;
373 					body_aabb.pos.y = body_aabb.pos.y + vector.y * dir;
374 					speed_mult.y = 0;
375 					if (dir == 1)
376 					{
377 						onGround = true;
378 					}
379 				}
380 				else // z
381 				{
382 					int dir = cell_aabb.pos.z < body_aabb.pos.z ? 1 : -1;
383 					body_aabb.pos.z = body_aabb.pos.z + vector.z * dir;
384 					speed_mult.z = 0;
385 				}
386 			}
387 		}
388 		}
389 		}
390 		}
391 
392 		point.x = body_aabb.pos.x;
393 		point.z = body_aabb.pos.z;
394 		point.y = body_aabb.pos.y - body_aabb.size.y;
395 
396 		return speed_mult;
397 	}
398 }
399 
400 
401 V limitingFriction(V)(V currentSpeed, V currentAcceleration, float maxSpeed)
402 {
403 	float speedScalar = currentSpeed.length;
404 	float frictionMult = speedScalar / maxSpeed;
405 	V frictionAcceleraion = -currentAcceleration * frictionMult;
406 	return frictionAcceleraion;
407 }
408 
409 float limitingFriction(float currentSpeed, float currentAcceleration, float maxSpeed)
410 {
411 	float frictionMult = currentSpeed / maxSpeed;
412 	float frictionAcceleraion = -currentAcceleration * frictionMult;
413 	return frictionAcceleraion;
414 }
415 
416 struct Aabb
417 {
418 	vec3 pos;
419 	vec3 size;
420 
421 	// returns collides, vector
422 	bool collides(Aabb other) {
423 		vec3 delta = (other.pos - pos).abs;
424 		vec3 min_distance = size + other.size;
425 		return delta.x < min_distance.x && delta.y < min_distance.y && delta.z < min_distance.z;
426 	}
427 
428 	vec3 intersectionSize(Aabb other) {
429 		float x = pos.x - size.x;
430 		float y = pos.y - size.y;
431 		float z = pos.z - size.z;
432 		float x2 = x + size.x*2;
433 		float y2 = y + size.y*2;
434 		float z2 = z + size.z*2;
435 
436 		float o_x = other.pos.x - other.size.x;
437 		float o_y = other.pos.y - other.size.y;
438 		float o_z = other.pos.z - other.size.z;
439 		float o_x2 = o_x + other.size.x*2;
440 		float o_y2 = o_y + other.size.y*2;
441 		float o_z2 = o_z + other.size.z*2;
442 
443 		float x_min = max(x, o_x);
444 		float y_min = max(y, o_y);
445 		float z_min = max(z, o_z);
446 		float x_max = min(x2, o_x2);
447 		float y_max = min(y2, o_y2);
448 		float z_max = min(z2, o_z2);
449 
450 		return vec3(x_max - x_min, y_max - y_min, z_max - z_min);
451 	}
452 }