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