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