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 }