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 module voxelman.world.clientworld; 7 8 import std.datetime : MonoTime, Duration, usecs, dur; 9 import derelict.imgui.imgui; 10 import voxelman.log; 11 import netlib; 12 import pluginlib; 13 import anchovy.irenderer; 14 import voxelman.math; 15 import voxelman.geometry.cube; 16 import voxelman.geometry.box; 17 import voxelman.utils.textformatter; 18 19 import voxelman.core.config; 20 import voxelman.core.events; 21 import voxelman.net.events; 22 import voxelman.utils.compression; 23 import voxelman.container.hashset; 24 25 import voxelman.block.plugin; 26 import voxelman.blockentity.plugin; 27 import voxelman.config.configmanager : ConfigOption, ConfigManager; 28 import voxelman.eventdispatcher.plugin : EventDispatcherPlugin; 29 import voxelman.gui.plugin : GuiPlugin; 30 import voxelman.graphics.plugin; 31 import voxelman.input.keybindingmanager; 32 import voxelman.session.client; 33 import voxelman.net.plugin : NetServerPlugin, NetClientPlugin; 34 import voxelman.dbg.plugin; 35 36 import voxelman.net.packets; 37 import voxelman.core.packets; 38 39 import voxelman.world.block; 40 import voxelman.world.storage; 41 import voxelman.world.storage.dimensionobservermanager; 42 import voxelman.world.blockentity.blockentityaccess; 43 import voxelman.world.blockentity.blockentitydata; 44 45 import voxelman.world.mesh.chunkmeshman; 46 47 struct IdMapManagerClient 48 { 49 void delegate(string[])[string] onMapReceivedHandlers; 50 51 void regIdMapHandler(string mapName, void delegate(string[]) onMapReceived) 52 { 53 onMapReceivedHandlers[mapName] = onMapReceived; 54 } 55 } 56 57 string fmtDur(Duration dur) 58 { 59 import std..string : format; 60 int seconds, msecs, usecs; 61 dur.split!("seconds", "msecs", "usecs")(seconds, msecs, usecs); 62 return format("%s.%03s,%03ss", seconds, msecs, usecs); 63 } 64 65 //version = DBG_COMPR; 66 final class ClientWorld : IPlugin 67 { 68 private: 69 EventDispatcherPlugin evDispatcher; 70 NetClientPlugin connection; 71 GraphicsPlugin graphics; 72 IRenderer renderer; 73 ClientSession session; 74 BlockPluginClient blockPlugin; 75 BlockEntityClient blockEntityPlugin; 76 77 ConfigOption numWorkersOpt; 78 Debugger dbg; 79 80 size_t wastedClientLoads; 81 82 public: 83 ChunkManager chunkManager; 84 ChunkObserverManager chunkObserverManager; 85 IdMapManagerClient idMapManager; 86 WorldAccess worldAccess; 87 BlockEntityAccess entityAccess; 88 ChunkMeshMan chunkMeshMan; 89 TimestampType currentTimestamp; 90 HashSet!ChunkWorldPos chunksToRemesh; 91 92 DimensionManager dimMan; 93 94 // toggles/debug 95 bool doUpdateObserverPosition = true; 96 size_t dbg_totalLoadedChunks; 97 size_t dbg_lastFrameLoadedChunks; 98 size_t dbg_meshesRenderedSolid; 99 size_t dbg_meshesRenderedSemitransparent; 100 size_t dbg_vertsRendered; 101 size_t dbg_trisRendered; 102 103 void resetCounters() 104 { 105 dbg_meshesRenderedSolid = 0; 106 dbg_meshesRenderedSemitransparent = 0; 107 dbg_vertsRendered = 0; 108 dbg_trisRendered = 0; 109 dbg_lastFrameLoadedChunks = dbg_totalLoadedChunks; 110 } 111 112 // Observer data 113 vec3 updatedCameraPos; 114 ChunkWorldPos observerPosition; 115 ubyte positionKey; 116 SessionId observerSessionId; 117 118 ConfigOption viewRadiusOpt; 119 int viewRadius; 120 121 // Graphics stuff 122 bool isCullingEnabled = true; 123 bool wireframeMode = false; 124 125 // Send position interval 126 double sendPositionTimer = 0; 127 enum sendPositionInterval = 0.1; 128 ChunkWorldPos prevChunkPos; 129 130 StringMap serverStrings; 131 132 mixin IdAndSemverFrom!"voxelman.world.plugininfo"; 133 134 override void registerResourceManagers(void delegate(IResourceManager) registerHandler) {} 135 136 override void registerResources(IResourceManagerRegistry resmanRegistry) 137 { 138 ConfigManager config = resmanRegistry.getResourceManager!ConfigManager; 139 numWorkersOpt = config.registerOption!int("num_workers", 4); 140 viewRadiusOpt = config.registerOption!int("view_distance", DEFAULT_VIEW_RADIUS); 141 142 KeyBindingManager keyBindingMan = resmanRegistry.getResourceManager!KeyBindingManager; 143 keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_RIGHT_BRACKET, "key.incViewRadius", null, &onIncViewRadius)); 144 keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_LEFT_BRACKET, "key.decViewRadius", null, &onDecViewRadius)); 145 keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_U, "key.togglePosUpdate", null, &onTogglePositionUpdate)); 146 keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_F2, "key.toggleChunkGrid", null, &onToggleChunkGrid)); 147 keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_F5, "key.remesh", null, &onRemeshViewBox)); 148 keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_F1, "key.chunkmeta", null, &onPrintChunkMeta)); 149 keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_C, "key.toggleCulling", null, &onToggleCulling)); 150 keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_Y, "key.toggleWireframe", null, &onToggleWireframe)); 151 152 dbg = resmanRegistry.getResourceManager!Debugger; 153 } 154 155 // stubs 156 private void handleLoadChunk(ChunkWorldPos) {} 157 private void handleChunkObserverAdded(ChunkWorldPos, SessionId) {} 158 159 override void preInit() 160 { 161 chunkManager = new ChunkManager(); 162 worldAccess = new WorldAccess(chunkManager); 163 entityAccess = new BlockEntityAccess(chunkManager); 164 165 chunkManager.setup(NUM_CHUNK_LAYERS); 166 chunkManager.setLayerInfo(ChunkLayerInfo(BLOCK_METADATA_UNIFORM_FILL_BITS), METADATA_LAYER); 167 168 chunkManager.loadChunkHandler = &handleLoadChunk; 169 chunkManager.cancelLoadChunkHandler = &handleLoadChunk; 170 chunkManager.isLoadCancelingEnabled = true; 171 chunkManager.isChunkSavingEnabled = false; 172 chunkManager.onChunkRemovedHandlers ~= &chunkMeshMan.onChunkRemoved; 173 174 chunkMeshMan.getDimensionBorders = &dimMan.dimensionBorders; 175 176 chunkObserverManager = new ChunkObserverManager(); 177 chunkObserverManager.changeChunkNumObservers = &chunkManager.setExternalChunkObservers; 178 chunkObserverManager.chunkObserverAdded = &handleChunkObserverAdded; 179 chunkObserverManager.loadQueueSpaceAvaliable = () => size_t.max; 180 181 idMapManager.regIdMapHandler("string_map", &onServerStringMapReceived); 182 } 183 184 override void init(IPluginManager pluginman) 185 { 186 import std.algorithm.comparison : clamp; 187 numWorkersOpt.set(clamp(numWorkersOpt.get!uint, 1, 16)); 188 189 viewRadius = viewRadiusOpt.get!uint; 190 // duplicated code 191 viewRadius = clamp(viewRadius, MIN_VIEW_RADIUS, MAX_VIEW_RADIUS); 192 193 session = pluginman.getPlugin!ClientSession; 194 195 auto gui = pluginman.getPlugin!GuiPlugin; 196 auto debugClient = pluginman.getPlugin!DebugClient; 197 debugClient.registerDebugGuiHandler(&showDebugGuiPosition, INFO_ORDER, "Position"); 198 debugClient.registerDebugGuiHandler(&showDebugGuiSettings, SETTINGS_ORDER, "Settings"); 199 debugClient.registerDebugGuiHandler(&showDebugGuiGraphics, DEBUG_ORDER, "Graphics"); 200 debugClient.registerDebugGuiHandler(&showDebugGuiChunks, DEBUG_ORDER, "Chunks"); 201 202 blockPlugin = pluginman.getPlugin!BlockPluginClient; 203 blockEntityPlugin = pluginman.getPlugin!BlockEntityClient; 204 chunkMeshMan.init(chunkManager, blockPlugin.getBlocks(), 205 blockEntityPlugin.blockEntityInfos(), numWorkersOpt.get!uint); 206 207 evDispatcher = pluginman.getPlugin!EventDispatcherPlugin; 208 evDispatcher.subscribeToEvent(&handlePreUpdateEvent); 209 evDispatcher.subscribeToEvent(&handlePostUpdateEvent); 210 evDispatcher.subscribeToEvent(&handleGameStopEvent); 211 evDispatcher.subscribeToEvent(&handleSendClientSettingsEvent); 212 evDispatcher.subscribeToEvent(&drawSolid); 213 214 connection = pluginman.getPlugin!NetClientPlugin; 215 connection.registerPacketHandler!ChunkDataPacket(&handleChunkDataPacket); 216 connection.registerPacketHandler!FillBlockBoxPacket(&handleFillBlockBoxPacket); 217 connection.registerPacketHandler!MultiblockChangePacket(&handleMultiblockChangePacket); 218 connection.registerPacketHandler!PlaceBlockEntityPacket(&handlePlaceBlockEntityPacket); 219 connection.registerPacketHandler!RemoveBlockEntityPacket(&handleRemoveBlockEntityPacket); 220 connection.registerPacketHandler!IdMapPacket(&handleIdMapPacket); 221 222 graphics = pluginman.getPlugin!GraphicsPlugin; 223 224 worldAccess.blockInfos = blockPlugin.getBlocks(); 225 } 226 227 override void postInit() 228 { 229 renderer = graphics.renderer; 230 } 231 232 WorldBox calcClampedBox(ChunkWorldPos cwp, int boxRadius) 233 { 234 int size = boxRadius*2 + 1; 235 return WorldBox(cast(ivec3)(cwp.ivector3 - boxRadius), 236 ivec3(size, size, size), cwp.w).intersection(dimMan.dimensionBorders(cwp.w)); 237 } 238 239 private void handleIdMapPacket(ubyte[] packetData) 240 { 241 auto packet = unpackPacket!IdMapPacket(packetData); 242 if (auto h = idMapManager.onMapReceivedHandlers.get(packet.mapName, null)) 243 { 244 h(packet.names); 245 } 246 } 247 248 private void onServerStringMapReceived(string[] strings) 249 { 250 serverStrings.load(strings); 251 } 252 253 private void onTogglePositionUpdate(string) 254 { 255 doUpdateObserverPosition = !doUpdateObserverPosition; 256 } 257 258 private void onToggleChunkGrid(string) { 259 chunkDebug_showGrid = !chunkDebug_showGrid; 260 } 261 262 private void onRemeshViewBox(string) { 263 WorldBox box = chunkObserverManager.getObserverBox(observerSessionId); 264 remeshBox(box, true); 265 } 266 267 private void onPrintChunkMeta(string) { 268 import voxelman.world.block : printChunkMetadata; 269 auto cwp = observerPosition; 270 auto snap = chunkManager.getChunkSnapshot(cwp, BLOCK_LAYER); 271 272 if (snap.isNull) { 273 infof("No snapshot for %s", cwp); 274 return; 275 } 276 printChunkMetadata(snap.metadata); 277 } 278 279 private void handlePreUpdateEvent(ref PreUpdateEvent event) 280 { 281 resetCounters(); 282 ++currentTimestamp; 283 284 if (doUpdateObserverPosition) 285 { 286 observerPosition = ChunkWorldPos( 287 BlockWorldPos(graphics.camera.position, observerPosition.w)); 288 } 289 290 updateObserverPosition(); 291 chunkObserverManager.update(); 292 chunkMeshMan.update(); 293 } 294 295 private void showDebugGuiPosition() 296 { 297 // heading 298 vec3 target = graphics.camera.target; 299 vec2 heading = graphics.camera.heading; 300 igTextf("Heading: %.1f %.1f", heading.x, heading.y); 301 igTextf("Target: X %.1f Y %.1f Z %.1f", target.x, target.y, target.z); 302 303 // position 304 vec3 pos = graphics.camera.position; 305 igTextf("Pos: X %.1f Y %.1f Z %.1f", pos.x, pos.y, pos.z); 306 ChunkWorldPos chunkPos = observerPosition; 307 igTextf("Chunk: %s %s %s", chunkPos.x, chunkPos.y, chunkPos.z); 308 } 309 310 private void showDebugGuiSettings() 311 { 312 igTextf("Dimension: %s", observerPosition.w); igSameLine(); 313 if (igButton("-##decDimension")) decDimension(); igSameLine(); 314 if (igButton("+##incDimension")) incDimension(); 315 316 igTextf("View radius: %s", viewRadius); igSameLine(); 317 if (igButton("-##decVRadius")) decViewRadius(); igSameLine(); 318 if (igButton("+##incVRadius")) incViewRadius(); 319 320 igCheckbox("[U]pdate observer pos", &doUpdateObserverPosition); 321 } 322 323 private void showDebugGuiGraphics() 324 { 325 if (igCollapsingHeader("Graphics")) 326 { 327 size_t dbg_meshesVisible = chunkMeshMan.chunkMeshes[0].length + chunkMeshMan.chunkMeshes[1].length; 328 size_t dbg_totalRendered = dbg_meshesRenderedSemitransparent + dbg_meshesRenderedSolid; 329 igTextf("(S/ST)/total (%s/%s)/%s/%s %.0f%%", 330 dbg_meshesRenderedSolid, dbg_meshesRenderedSemitransparent, dbg_totalRendered, dbg_meshesVisible, 331 dbg_meshesVisible ? cast(float)dbg_totalRendered/dbg_meshesVisible*100.0 : 0); 332 igTextf("Vertices %s", dbg_vertsRendered); 333 igTextf("Triangles %s", dbg_trisRendered); 334 import anchovy.vbo; 335 igTextf("Buffers: %s Mem: %s", 336 Vbo.numAllocated, 337 DigitSeparator!(long, 3, ' ')(chunkMeshMan.totalMeshDataBytes)); 338 } 339 } 340 341 private void showDebugGuiChunks() 342 { 343 if (igCollapsingHeader("Chunks")) 344 { 345 drawDebugChunkInfoGui(); 346 347 igTextf("Chunks per frame loaded: %s", dbg_totalLoadedChunks - dbg_lastFrameLoadedChunks); 348 dbg_lastFrameLoadedChunks = dbg_totalLoadedChunks; 349 igTextf("Chunks total loaded: %s", dbg_totalLoadedChunks); 350 igTextf("Chunk mem %s", DigitSeparator!(long, 3, ' ')(chunkManager.totalLayerDataBytes)); 351 352 with(chunkMeshMan) { 353 igTextf("Chunks to mesh: %s", numMeshChunkTasks); 354 igTextf("New meshes: %s", newChunkMeshes.length); 355 size_t sum; 356 foreach(ref w; meshWorkers.workers) sum += w.taskQueue.length; 357 igTextf("Task Queues: %s", sum); 358 sum = 0; 359 foreach(ref w; meshWorkers.workers) sum += w.resultQueue.length; 360 igTextf("Res Queues: %s", sum); 361 float percent = totalMeshedChunks > 0 ? cast(float)totalMeshes / totalMeshedChunks * 100 : 0.0; 362 igTextf("Meshed/Meshes %s/%s %.0f%%", totalMeshedChunks, totalMeshes, percent); 363 } 364 } 365 } 366 367 private bool chunkDebug_showGrid; 368 private int chunkDebug_viewRadius = 2; 369 private bool chunkDebug_showUniform; 370 private bool chunkDebug_showSideMetadata; 371 private bool chunkDebug_showBlockEntities; 372 private bool chunkDebug_showWastedMeshes; 373 private bool chunkDebug_showChunkLayers; 374 375 private void drawDebugChunkInfoGui() 376 { 377 // debug view radius 378 igTextf("Debug radius: %s", chunkDebug_viewRadius); 379 igSameLine(); 380 if (igButton("-##decDebugRadius")) 381 --chunkDebug_viewRadius; 382 igSameLine(); 383 if (igButton("+##incDebugRadius")) 384 ++chunkDebug_viewRadius; 385 386 igCheckbox("show grid", &chunkDebug_showGrid); 387 igCheckbox("show uniform", &chunkDebug_showUniform); 388 igCheckbox("show side meta", &chunkDebug_showSideMetadata); 389 igCheckbox("show block entities", &chunkDebug_showBlockEntities); 390 igCheckbox("show wasted meshes", &chunkDebug_showWastedMeshes); 391 igCheckbox("show chunk layers", &chunkDebug_showChunkLayers); 392 } 393 394 private void drawDebugChunkInfo() 395 { 396 ChunkWorldPos chunkPos = BlockWorldPos(graphics.camera.position, currentDimension); 397 chunkDebug_viewRadius = clamp(chunkDebug_viewRadius, 0, 10); 398 WorldBox nearBox = calcBox(chunkPos, chunkDebug_viewRadius); 399 400 if (chunkDebug_showGrid) drawDebugChunkGrid(nearBox); 401 if (chunkDebug_showSideMetadata) drawDebugChunkSideMetadata(nearBox); 402 if (chunkDebug_showUniform) drawDebugChunkUniform(nearBox); 403 if (chunkDebug_showBlockEntities) drawDebugBlockentity(nearBox); 404 if (chunkDebug_showWastedMeshes) chunkMeshMan.drawDebug(graphics.debugBatch); 405 if (chunkDebug_showChunkLayers) drawDebugChunkLayers(nearBox); 406 } 407 408 private void drawDebugChunkSideMetadata(WorldBox box) 409 { 410 foreach(pos; box.positions) 411 { 412 vec3 blockPos = pos * CHUNK_SIZE; 413 auto snap = chunkManager.getChunkSnapshot(ChunkWorldPos(pos, box.dimension), BLOCK_LAYER); 414 if (snap.isNull) continue; 415 416 foreach(ubyte side; 0..6) { 417 Solidity solidity = chunkSideSolidity(snap.metadata, cast(CubeSide)side); 418 static Color4ub[3] colors = [Colors.white, Colors.gray, Colors.black]; 419 Color4ub color = colors[solidity]; 420 graphics.debugBatch.putCubeFace(blockPos + CHUNK_SIZE/2, vec3(2,2,2), cast(CubeSide)side, color, true); 421 } 422 } 423 } 424 425 private void drawDebugChunkUniform(WorldBox box) 426 { 427 foreach(pos; box.positions) { 428 vec3 blockPos = pos * CHUNK_SIZE; 429 auto snap = chunkManager.getChunkSnapshot(ChunkWorldPos(pos, box.dimension), BLOCK_LAYER); 430 if (!snap.isNull && snap.isUniform) { 431 graphics.debugBatch.putCube(blockPos + CHUNK_SIZE/2-2, vec3(6,6,6), Colors.green, false); 432 } 433 } 434 } 435 436 private void drawDebugBlockentity(WorldBox box) 437 { 438 foreach(pos; box.positions) 439 { 440 ivec3 chunkPos = pos * CHUNK_SIZE; 441 442 auto snap = chunkManager.getChunkSnapshot( 443 ChunkWorldPos(pos, box.dimension), ENTITY_LAYER); 444 445 if (snap.isNull) continue; 446 auto map = getHashMapFromLayer(snap); 447 foreach(id, entity; map) 448 { 449 if (BlockEntityData(entity).type == BlockEntityType.localBlockEntity) 450 { 451 auto pos = BlockChunkPos(id); 452 auto entityPos = chunkPos + pos.vector; 453 graphics.debugBatch.putCube(vec3(entityPos)+0.25, vec3(0.5,0.5,0.5), Colors.red, true); 454 } 455 else if (BlockEntityData(entity).type == BlockEntityType.foreignBlockEntity) 456 { 457 auto pos = BlockChunkPos(id); 458 auto entityPos = chunkPos + pos.vector; 459 graphics.debugBatch.putCube(vec3(entityPos)+0.25, vec3(0.5,0.5,0.5), Colors.orange, true); 460 } 461 } 462 } 463 } 464 465 private void drawDebugChunkGrid(WorldBox box) 466 { 467 vec3 gridPos = vec3(box.position*CHUNK_SIZE); 468 ivec3 gridCount = box.size+1; 469 vec3 gridOffset = vec3(CHUNK_SIZE, CHUNK_SIZE, CHUNK_SIZE); 470 graphics.debugBatch.put3dGrid(gridPos, gridCount, gridOffset, Colors.blue); 471 } 472 473 private void drawDebugChunkLayers(WorldBox box) 474 { 475 foreach(pos; box.positions) 476 { 477 auto cwp = ChunkWorldPos(pos, box.dimension); 478 ivec3 chunkPos = pos * CHUNK_SIZE + CHUNK_SIZE/2; 479 graphics.debugBatch.putCube(vec3(chunkPos)+vec3(0.75, 0, 0), vec3(0.25,1,1), Colors.red, true); 480 foreach(ubyte layer; 0..chunkManager.numLayers) 481 { 482 ivec3 layerBlockPos = chunkPos; 483 layerBlockPos.x += layer + 1; 484 if (chunkManager.hasSnapshot(cwp, layer)) 485 graphics.debugBatch.putCube(vec3(layerBlockPos)+0.25, vec3(0.5,0.5,0.5), Colors.white, true); 486 else 487 graphics.debugBatch.putCube(vec3(layerBlockPos)+0.25, vec3(0.5,0.5,0.5), Colors.black, false); 488 } 489 } 490 } 491 492 private void handlePostUpdateEvent(ref PostUpdateEvent event) 493 { 494 chunkManager.commitSnapshots(currentTimestamp); 495 //chunkMeshMan.remeshChangedChunks(chunkManager.getModifiedChunks()); 496 chunkManager.clearModifiedChunks(); 497 chunkMeshMan.remeshChangedChunks(chunksToRemesh); 498 chunksToRemesh.clear(); 499 500 if (doUpdateObserverPosition) 501 sendPosition(event.deltaTime); 502 503 dbg.setVar("wasted client loads", wastedClientLoads); 504 drawDebugChunkInfo(); 505 } 506 507 import dlib.geometry.frustum; 508 void drawSolid(ref RenderSolid3dEvent event) 509 { 510 renderer.wireFrameMode(wireframeMode); 511 512 Matrix4f vp = graphics.camera.perspective * graphics.camera.cameraToClipMatrix; 513 Frustum frustum; 514 frustum.fromMVP(vp); 515 516 renderer.faceCulling(true); 517 renderer.faceCullMode(FaceCullMode.back); 518 519 drawMeshes(chunkMeshMan.chunkMeshes[0].byValue, frustum, 520 graphics.solidShader3d, 521 dbg_meshesRenderedSolid); 522 523 renderer.alphaBlending(true); 524 renderer.depthWrite(false); 525 526 graphics.transparentShader3d.bind; 527 graphics.transparentShader3d.setTransparency(0.5f); 528 drawMeshes(chunkMeshMan.chunkMeshes[1].byValue, frustum, 529 graphics.transparentShader3d, 530 dbg_meshesRenderedSemitransparent); 531 532 renderer.faceCulling(false); 533 renderer.depthWrite(true); 534 renderer.alphaBlending(false); 535 536 if (wireframeMode) 537 renderer.wireFrameMode(false); 538 } 539 540 private void drawMeshes(R, S)(R meshes, Frustum frustum, ref S shader, ref size_t meshCounter) 541 { 542 shader.bind; 543 shader.setVP(graphics.camera.cameraMatrix, graphics.camera.perspective); 544 545 foreach(const ref mesh; meshes) 546 { 547 if (isCullingEnabled) // Frustum culling 548 { 549 import dlib.geometry.aabb; 550 vec3 vecMin = mesh.position; 551 vec3 vecMax = vecMin + CHUNK_SIZE; 552 AABB aabb = boxFromMinMaxPoints(vecMin, vecMax); 553 auto intersects = frustum.intersectsAABB(aabb); 554 if (!intersects) continue; 555 } 556 557 Matrix4f modelMatrix = translationMatrix!float(mesh.position); 558 shader.setModel(modelMatrix); 559 560 mesh.render(); 561 562 ++meshCounter; 563 dbg_vertsRendered += mesh.numVertexes; 564 dbg_trisRendered += mesh.numTris; 565 } 566 shader.unbind; 567 } 568 569 private void handleGameStopEvent(ref GameStopEvent gameStopEvent) 570 { 571 import core.thread; 572 while(chunkMeshMan.numMeshChunkTasks > 0) 573 { 574 chunkMeshMan.update(); 575 } 576 chunkMeshMan.stop(); 577 } 578 579 private void handleSendClientSettingsEvent(ref SendClientSettingsEvent event) 580 { 581 connection.send(ViewRadiusPacket(viewRadius)); 582 } 583 584 private void handleChunkDataPacket(ubyte[] packetData) 585 { 586 auto packet = unpackPacketNoDup!ChunkDataPacket(packetData); 587 //tracef("Received %s ChunkDataPacket(%s,%s)", packetData.length, 588 // packet.chunkPos, packet.blockData.blocks.length); 589 590 ChunkLayerItem[MAX_CHUNK_LAYERS] layers; 591 auto cwp = ChunkWorldPos(packet.chunkPos); 592 593 ubyte numChunkLayers; 594 foreach(layer; packet.layers) 595 { 596 layers[numChunkLayers] = fromBlockData(layer); 597 ++numChunkLayers; 598 } 599 600 onChunkLoaded(cwp, layers[0..numChunkLayers]); 601 } 602 603 void onChunkLoaded(ChunkWorldPos cwp, ChunkLayerItem[] layers) 604 { 605 //tracef("onChunkLoaded %s added %s", cwp, chunkManager.isChunkAdded(cwp)); 606 ++dbg_totalLoadedChunks; 607 608 if (chunkManager.isChunkLoaded(cwp)) 609 { 610 // TODO possible bug, copyLayer could be called when write buffer is not empty 611 foreach(layer; layers) 612 { 613 WriteBuffer* writeBuffer = chunkManager.getOrCreateWriteBuffer(cwp, layer.layerId); 614 copyLayer(layer, writeBuffer.layer); 615 } 616 } 617 else if (chunkManager.isChunkAdded(cwp)) 618 { 619 foreach(ref layer; layers) 620 { 621 if (!layer.isUniform) 622 { 623 ubyte[] data = allocLayerArray(layer.getArray!ubyte); 624 layer.dataLength = cast(LayerDataLenType)data.length; 625 layer.dataPtr = data.ptr; 626 } 627 } 628 chunkManager.onSnapshotLoaded(cwp, layers, true); 629 } 630 else 631 { 632 // we received chunk data for unloaded chunk. Ignore it. 633 ++wastedClientLoads; 634 return; 635 } 636 637 foreach(ChunkWorldPos pos; calcClampedBox(cwp, 1)) 638 chunksToRemesh.put(pos); 639 } 640 641 private void handleMultiblockChangePacket(ubyte[] packetData) 642 { 643 auto packet = unpackPacket!MultiblockChangePacket(packetData); 644 auto cwp = ChunkWorldPos(packet.chunkPos); 645 646 worldAccess.applyBlockChanges(cwp, packet.blockChanges); 647 648 foreach(ChunkWorldPos pos; calcClampedBox(cwp, 1)) 649 chunksToRemesh.put(pos); 650 } 651 652 private void handleFillBlockBoxPacket(ubyte[] packetData) 653 { 654 auto packet = unpackPacketNoDup!FillBlockBoxPacket(packetData); 655 656 worldAccess.fillBox(packet.box, packet.blockId, packet.blockMeta); 657 onBlockBoxChanged(packet.box); 658 } 659 660 void onBlockBoxChanged(WorldBox blockBox) 661 { 662 WorldBox observedBox = chunkObserverManager.getObserverBox(observerSessionId); 663 WorldBox modifiedBox = calcModifiedMeshesBox(blockBox); 664 WorldBox box = worldBoxIntersection(observedBox, modifiedBox); 665 666 foreach(ChunkWorldPos pos; box) 667 chunksToRemesh.put(pos); 668 } 669 670 private void handlePlaceBlockEntityPacket(ubyte[] packetData) 671 { 672 auto packet = unpackPacket!PlaceBlockEntityPacket(packetData); 673 placeEntity(packet.box, packet.data, 674 worldAccess, entityAccess); 675 onBlockBoxChanged(packet.box); 676 } 677 678 private void handleRemoveBlockEntityPacket(ubyte[] packetData) 679 { 680 auto packet = unpackPacket!RemoveBlockEntityPacket(packetData); 681 WorldBox changedBox = removeEntity(BlockWorldPos(packet.blockPos), 682 blockEntityPlugin.blockEntityInfos, worldAccess, entityAccess, /*AIR*/1); 683 onBlockBoxChanged(changedBox); 684 } 685 686 private void remeshBox(WorldBox box, bool printTime = false) 687 { 688 MonoTime startTime = MonoTime.currTime; 689 690 void onRemeshDone(size_t chunksRemeshed, Duration totalDuration) { 691 auto duration = MonoTime.currTime - startTime; 692 auto avg = totalDuration / chunksRemeshed; 693 infof("Remeshed %s chunks in\ttotal %s\tavg %s\tsingle thread %s", chunksRemeshed, fmtDur(totalDuration), fmtDur(avg), fmtDur(duration)); 694 } 695 696 HashSet!ChunkWorldPos remeshedChunks; 697 foreach(pos; box.positions) { 698 remeshedChunks.put(ChunkWorldPos(pos, box.dimension)); 699 } 700 701 if (printTime) 702 { 703 size_t numMeshed = chunkMeshMan.remeshChangedChunks(remeshedChunks, &onRemeshDone); 704 auto submitDuration = MonoTime.currTime - startTime; 705 auto avg = submitDuration / numMeshed; 706 infof("%s tasks submitted in %s, avg per task %s", numMeshed, submitDuration.fmtDur, avg.fmtDur); 707 } 708 else 709 chunkMeshMan.remeshChangedChunks(remeshedChunks); 710 } 711 712 void sendPosition(double dt) 713 { 714 if (session.isSpawned) 715 { 716 sendPositionTimer += dt; 717 if (sendPositionTimer > sendPositionInterval || 718 observerPosition != prevChunkPos) 719 { 720 connection.send(ClientPositionPacket( 721 ClientDimPos( 722 graphics.camera.position, 723 graphics.camera.heading), 724 observerPosition.w, positionKey)); 725 726 if (sendPositionTimer < sendPositionInterval) 727 sendPositionTimer = 0; 728 else 729 sendPositionTimer -= sendPositionInterval; 730 } 731 } 732 733 prevChunkPos = observerPosition; 734 } 735 736 void setDimensionBorders(DimensionId dim, Box borders) 737 { 738 DimensionInfo* dimInfo = dimMan.getOrCreate(dim); 739 dimInfo.borders = borders; 740 updateObserverPosition(); 741 } 742 743 void setCurrentDimension(DimensionId dimension, ubyte positionKey) { 744 observerPosition.w = dimension; 745 this.positionKey = positionKey; 746 updateObserverPosition(); 747 } 748 749 bool isBlockSolid(ivec3 blockWorldPos) { 750 auto block = worldAccess.getBlock( 751 BlockWorldPos(blockWorldPos, observerPosition.w)); 752 return block != 0 && blockPlugin.getBlocks()[block].isVisible; 753 } 754 755 DimensionId currentDimension() @property { 756 return observerPosition.w; 757 } 758 759 Box currentDimensionBorders() @property { 760 return dimMan.dimensionBorders(observerPosition.dimension); 761 } 762 763 void incDimension() { 764 string com = cast(string)makeFormattedText("dim %s", currentDimension() + 1); 765 connection.send(CommandPacket(com)); 766 } 767 void decDimension() { 768 string com = cast(string)makeFormattedText("dim %s", currentDimension() - 1); 769 connection.send(CommandPacket(com)); 770 } 771 772 void updateObserverPosition() { 773 if (session.isSpawned) { 774 if (observerSessionId != session.thisSessionId) { 775 chunkObserverManager.removeObserver(observerSessionId); 776 observerSessionId = cast(SessionId)session.thisSessionId; 777 } 778 779 auto borders = dimMan.dimensionBorders(observerPosition.dimension); 780 chunkObserverManager.changeObserverBox( 781 observerSessionId, observerPosition, viewRadius, borders); 782 } 783 } 784 785 void onIncViewRadius(string) { 786 incViewRadius(); 787 } 788 789 void onDecViewRadius(string) { 790 decViewRadius(); 791 } 792 793 void incViewRadius() { 794 setViewRadius(getViewRadius() + 1); 795 } 796 797 void decViewRadius() { 798 setViewRadius(getViewRadius() - 1); 799 } 800 801 int getViewRadius() { 802 return viewRadius; 803 } 804 805 void setViewRadius(int newViewRadius) { 806 auto oldViewRadius = viewRadius; 807 // duplicated code 808 viewRadius = clamp(newViewRadius, MIN_VIEW_RADIUS, MAX_VIEW_RADIUS); 809 810 if (oldViewRadius != viewRadius) 811 { 812 connection.send(ViewRadiusPacket(viewRadius)); 813 } 814 } 815 816 void onToggleCulling(string) { 817 isCullingEnabled = !isCullingEnabled; 818 } 819 820 void onToggleWireframe(string) { 821 wireframeMode = !wireframeMode; 822 } 823 }