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 }