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 }