diff --git a/Src/Client/Abstract.hpp b/Src/Client/Abstract.hpp index 185bd59..429b202 100644 --- a/Src/Client/Abstract.hpp +++ b/Src/Client/Abstract.hpp @@ -32,7 +32,7 @@ struct GlobalTime { struct VoxelCube { DefVoxelId_c VoxelId; - Pos::Local256_u Left, Right; + Pos::Local256_u Left, Size; }; struct Node { diff --git a/Src/Client/ServerSession.cpp b/Src/Client/ServerSession.cpp index e415b5d..dc49660 100644 --- a/Src/Client/ServerSession.cpp +++ b/Src/Client/ServerSession.cpp @@ -157,7 +157,8 @@ void ServerSession::onResize(uint32_t width, uint32_t height) { } void ServerSession::onChangeFocusState(bool isFocused) { - + if(!isFocused) + CursorMode = EnumCursorMoveMode::Default; } void ServerSession::onCursorPosChange(int32_t width, int32_t height) { @@ -273,7 +274,7 @@ void ServerSession::atFreeDrawTime(GlobalTime gTime, float dTime) { float deltaTime = 1-std::min(gTime-PYR_At, 1/PYR_TIME_DELTA)*PYR_TIME_DELTA; glm::quat quat = - glm::angleAxis(PYR.x-deltaTime*PYR_Offset.x, glm::vec3(-1.f, 0.f, 0.f)) + glm::angleAxis(PYR.x-deltaTime*PYR_Offset.x, glm::vec3(1.f, 0.f, 0.f)) * glm::angleAxis(PYR.y-deltaTime*PYR_Offset.y, glm::vec3(0.f, -1.f, 0.f)); RS->setCameraPos(0, Pos, quat); @@ -465,9 +466,9 @@ coro<> ServerSession::rP_Content(Net::AsyncSocket &sock) { cube.Left.X = co_await sock.read(); cube.Left.Y = co_await sock.read(); cube.Left.Z = co_await sock.read(); - cube.Right.X = co_await sock.read(); - cube.Right.Y = co_await sock.read(); - cube.Right.Z = co_await sock.read(); + cube.Size.X = co_await sock.read(); + cube.Size.Y = co_await sock.read(); + cube.Size.Z = co_await sock.read(); } PP_Content_ChunkVoxels *packet = new PP_Content_ChunkVoxels( diff --git a/Src/Client/Vulkan/VulkanRenderSession.cpp b/Src/Client/Vulkan/VulkanRenderSession.cpp index 6b723c1..194edf7 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.cpp +++ b/Src/Client/Vulkan/VulkanRenderSession.cpp @@ -3,6 +3,7 @@ #include "Client/Vulkan/Vulkan.hpp" #include "Common/Abstract.hpp" #include "assets.hpp" +#include "glm/trigonometric.hpp" #include #include #include @@ -194,9 +195,17 @@ void VulkanRenderSession::init(Vulkan *instance) { int width, height; bool hasAlpha; - ByteBuffer image = VK::loadPNG(std::ifstream("/home/mr_s/Workspace/Alpha/LuaVox/assets/grass.png"), width, height, hasAlpha); - uint16_t texId = VKCTX->MainTest.atlasAddTexture(width, height); - VKCTX->MainTest.atlasChangeTextureData(texId, (const uint32_t*) image.data()); + for(const char *path : { + "grass.png", + "tropical_rainforest_wood.png", + "willow_wood.png", + "xnether_blue_wood.png", + "xnether_purple_wood.png" + }) { + ByteBuffer image = VK::loadPNG(getResource(std::string("textures/") + path)->makeStream().Stream, width, height, hasAlpha); + uint16_t texId = VKCTX->MainTest.atlasAddTexture(width, height); + VKCTX->MainTest.atlasChangeTextureData(texId, (const uint32_t*) image.data()); + } /* x left -1 ~ right 1 @@ -221,15 +230,22 @@ void VulkanRenderSession::init(Vulkan *instance) { { std::vector cubes; - cubes.emplace_back(0, Pos::Local256_u{0, 0, 0}, Pos::Local256_u{1, 1, 1}); + cubes.emplace_back(0, Pos::Local256_u{0, 0, 0}, Pos::Local256_u{0, 0, 0}); + cubes.emplace_back(1, Pos::Local256_u{255, 0, 0}, Pos::Local256_u{0, 0, 0}); + cubes.emplace_back(1, Pos::Local256_u{0, 255, 0}, Pos::Local256_u{0, 0, 0}); + cubes.emplace_back(1, Pos::Local256_u{0, 0, 255}, Pos::Local256_u{0, 0, 0}); + cubes.emplace_back(2, Pos::Local256_u{255, 255, 0}, Pos::Local256_u{0, 0, 0}); + cubes.emplace_back(2, Pos::Local256_u{0, 255, 255}, Pos::Local256_u{0, 0, 0}); + cubes.emplace_back(2, Pos::Local256_u{255, 0, 255}, Pos::Local256_u{0, 0, 0}); + cubes.emplace_back(3, Pos::Local256_u{255, 255, 255}, Pos::Local256_u{0, 0, 0}); + + cubes.emplace_back(4, Pos::Local256_u{64, 64, 64}, Pos::Local256_u{127, 127, 127}); std::vector vertexs = generateMeshForVoxelChunks(cubes); if(!vertexs.empty()) { VKCTX->TestVoxel.emplace(VkInst, vertexs.size()*sizeof(VoxelVertexPoint)); - VoxelVertexPoint *result = (VoxelVertexPoint*) VKCTX->TestVoxel->mapMemory(); - std::copy(vertexs.data(), vertexs.data()+vertexs.size(), result); - TOS::Logger("Test").debug() << result[0].FX << " " << result[0].FY << " " << result[0].FZ; + std::copy(vertexs.data(), vertexs.data()+vertexs.size(), (VoxelVertexPoint*) VKCTX->TestVoxel->mapMemory()); VKCTX->TestVoxel->unMapMemory(); } } @@ -655,11 +671,19 @@ void VulkanRenderSession::beforeDraw() { } void VulkanRenderSession::drawWorld(GlobalTime gTime, float dTime, VkCommandBuffer drawCmd) { - glm::mat4 proj = glm::perspective(75, float(VkInst->Screen.Width)/float(VkInst->Screen.Height), 0.5, std::pow(2, 17)); - // Сместить в координаты игрока, повернуть относительно взгляда, ещё поворот на 180 и проецировать на экран + glm::mat4 proj = glm::perspective(glm::radians(75.f), float(VkInst->Screen.Width)/float(VkInst->Screen.Height), 0.5, std::pow(2, 17)); + + for(int i = 0; i < 4; i++) { + proj[1][i] *= -1; + proj[2][i] *= -1; + } + + + // Сместить в координаты игрока, повернуть относительно взгляда проецировать на экран + // Изначально взгляд в z-1 PCO.ProjView = glm::mat4(1); PCO.ProjView = glm::translate(PCO.ProjView, -glm::vec3(Pos)/float(Pos::Object_t::BS)); - PCO.ProjView = proj*glm::mat4(Quat)*glm::rotate(glm::mat4(1), glm::pi(), glm::vec3(0, 1, 0))*PCO.ProjView; + PCO.ProjView = proj*glm::mat4(Quat)*PCO.ProjView; PCO.Model = glm::mat4(1); vkCmdBindPipeline(drawCmd, VK_PIPELINE_BIND_POINT_GRAPHICS, NodeStaticOpaquePipeline); @@ -692,6 +716,7 @@ void VulkanRenderSession::drawWorld(GlobalTime gTime, float dTime, VkCommandBuff if(VKCTX->TestVoxel) { vkBuffer = *VKCTX->TestVoxel; + vkCmdBindVertexBuffers(drawCmd, 0, 1, &vkBuffer, &vkOffsets); vkCmdDraw(drawCmd, VKCTX->TestVoxel->getSize() / sizeof(VoxelVertexPoint), 1, 0, 0); } } @@ -706,9 +731,74 @@ std::vector VulkanRenderSession::generateMeshForVoxelChunks(co cube.Left.Y, cube.Left.Z, 0, - 0, - cube.Right.X-cube.Left.X, - cube.Right.Z-cube.Left.Z, + 0, 0, + cube.Size.X, + cube.Size.Z, + cube.VoxelId, + 0, 0, + 0 + ); + + out.emplace_back( + cube.Left.X, + cube.Left.Y, + cube.Left.Z, + 1, + 0, 0, + cube.Size.X, + cube.Size.Y, + cube.VoxelId, + 0, 0, + 0 + ); + + out.emplace_back( + cube.Left.X, + cube.Left.Y, + cube.Left.Z, + 2, + 0, 0, + cube.Size.Z, + cube.Size.Y, + cube.VoxelId, + 0, 0, + 0 + ); + + out.emplace_back( + cube.Left.X, + cube.Left.Y+cube.Size.Y+1, + cube.Left.Z, + 3, + 0, 0, + cube.Size.X, + cube.Size.Z, + cube.VoxelId, + 0, 0, + 0 + ); + + out.emplace_back( + cube.Left.X, + cube.Left.Y, + cube.Left.Z+cube.Size.Z+1, + 4, + 0, 0, + cube.Size.X, + cube.Size.Y, + cube.VoxelId, + 0, 0, + 0 + ); + + out.emplace_back( + cube.Left.X+cube.Size.X+1, + cube.Left.Y, + cube.Left.Z, + 5, + 0, 0, + cube.Size.Z, + cube.Size.Y, cube.VoxelId, 0, 0, 0 diff --git a/assets/shaders/chunk/voxel.geom b/assets/shaders/chunk/voxel.geom index 1afc36e..c0e5317 100644 --- a/assets/shaders/chunk/voxel.geom +++ b/assets/shaders/chunk/voxel.geom @@ -5,11 +5,12 @@ layout (triangle_strip, max_vertices = 4) out; layout(location = 0) in highp uvec3 Geometry[]; -layout(location = 0) out Fragment { +layout(location = 0) out FragmentObj { vec3 GeoPos; // Реальная позиция в мире - uint VoxMTL; // Материал вокселя + flat uint VoxMTL; // Материал вокселя vec2 LUV; -} fragment; + flat uint Place; +} Fragment; layout(push_constant) uniform UniformBufferObject { mat4 projview; @@ -43,195 +44,214 @@ void main() { uint voxMTL = ((Geometry[0].y >> 16) & 0xffff); vec2 luv = vec2(float(Geometry[0].z & 0x3fff)+0.5f, float((Geometry[0].z >> 14) & 0x3fff)+0.5f); + int place = int(Geometry[0].x >> 27) & 0x7; // Стартовая вершина vec4 tempVec = baseVec; tempVec = ubo.model*tempVec; - fragment.GeoPos = vec3(tempVec); - fragment.VoxMTL = voxMTL; - fragment.LUV = luv; + Fragment.GeoPos = vec3(tempVec); + Fragment.VoxMTL = voxMTL; + Fragment.LUV = luv; + Fragment.Place = place; gl_Position = ubo.projview*tempVec; EmitVertex(); - int place = int(Geometry[0].x >> 27) & 0x3; switch(place) { case 0: // xz tempVec = baseVec; - tempVec.x += size.x; + tempVec.z += size.y / 16.f; tempVec = ubo.model*tempVec; - fragment.GeoPos = vec3(tempVec); - fragment.VoxMTL = voxMTL; - fragment.LUV = luv+vec2(size.x, 0); + Fragment.GeoPos = tempVec.xyz; + Fragment.VoxMTL = voxMTL; + Fragment.LUV = luv+vec2(0, size.y); + Fragment.Place = place; gl_Position = ubo.projview*tempVec; EmitVertex(); tempVec = baseVec; - tempVec.z += size.y; + tempVec.x += size.x / 16.f; tempVec = ubo.model*tempVec; - fragment.GeoPos = vec3(tempVec); - fragment.VoxMTL = voxMTL; - fragment.LUV = luv+vec2(0, size.y); + Fragment.GeoPos = tempVec.xyz; + Fragment.VoxMTL = voxMTL; + Fragment.LUV = luv+vec2(size.x, 0); + Fragment.Place = place; gl_Position = ubo.projview*tempVec; EmitVertex(); tempVec = baseVec; - tempVec.x += size.x; - tempVec.z += size.y; + tempVec.x += size.x / 16.f; + tempVec.z += size.y / 16.f; tempVec = ubo.model*tempVec; - fragment.GeoPos = vec3(tempVec); - fragment.VoxMTL = voxMTL; - fragment.LUV = luv+vec2(size.x, size.y); + Fragment.GeoPos = tempVec.xyz; + Fragment.VoxMTL = voxMTL; + Fragment.LUV = luv+vec2(size.x, size.y); + Fragment.Place = place; gl_Position = ubo.projview*tempVec; EmitVertex(); break; case 1: // xy tempVec = baseVec; - tempVec.x += size.x; + tempVec.x += size.x / 16.f; tempVec = ubo.model*tempVec; - fragment.GeoPos = vec3(tempVec); - fragment.VoxMTL = voxMTL; - fragment.LUV = luv+vec2(size.x, 0); + Fragment.GeoPos = tempVec.xyz; + Fragment.VoxMTL = voxMTL; + Fragment.LUV = luv+vec2(size.x, 0); + Fragment.Place = place; gl_Position = ubo.projview*tempVec; EmitVertex(); tempVec = baseVec; - tempVec.y += size.y; + tempVec.y += size.y / 16.f; tempVec = ubo.model*tempVec; - fragment.GeoPos = vec3(tempVec); - fragment.VoxMTL = voxMTL; - fragment.LUV = luv+vec2(0, size.y); + Fragment.GeoPos = tempVec.xyz; + Fragment.VoxMTL = voxMTL; + Fragment.LUV = luv+vec2(0, size.y); + Fragment.Place = place; gl_Position = ubo.projview*tempVec; EmitVertex(); tempVec = baseVec; - tempVec.x += size.x; - tempVec.y += size.y; + tempVec.x += size.x / 16.f; + tempVec.y += size.y / 16.f; tempVec = ubo.model*tempVec; - fragment.GeoPos = vec3(tempVec); - fragment.VoxMTL = voxMTL; - fragment.LUV = luv+vec2(size.x, size.y); + Fragment.GeoPos = tempVec.xyz; + Fragment.VoxMTL = voxMTL; + Fragment.LUV = luv+vec2(size.x, size.y); + Fragment.Place = place; gl_Position = ubo.projview*tempVec; EmitVertex(); break; case 2: // zy tempVec = baseVec; - tempVec.z += size.x; + tempVec.y += size.y / 16.f; tempVec = ubo.model*tempVec; - fragment.GeoPos = vec3(tempVec); - fragment.VoxMTL = voxMTL; - fragment.LUV = luv+vec2(size.x, 0); + Fragment.GeoPos = tempVec.xyz; + Fragment.VoxMTL = voxMTL; + Fragment.LUV = luv+vec2(0, size.y); + Fragment.Place = place; gl_Position = ubo.projview*tempVec; EmitVertex(); tempVec = baseVec; - tempVec.y += size.y; + tempVec.z += size.x / 16.f; tempVec = ubo.model*tempVec; - fragment.GeoPos = vec3(tempVec); - fragment.VoxMTL = voxMTL; - fragment.LUV = luv+vec2(0, size.y); + Fragment.GeoPos = tempVec.xyz; + Fragment.VoxMTL = voxMTL; + Fragment.LUV = luv+vec2(size.x, 0); + Fragment.Place = place; gl_Position = ubo.projview*tempVec; EmitVertex(); tempVec = baseVec; - tempVec.z += size.x; - tempVec.y += size.y; + tempVec.z += size.x / 16.f; + tempVec.y += size.y / 16.f; tempVec = ubo.model*tempVec; - fragment.GeoPos = vec3(tempVec); - fragment.VoxMTL = voxMTL; - fragment.LUV = luv+vec2(size.x, size.y); + Fragment.GeoPos = tempVec.xyz; + Fragment.VoxMTL = voxMTL; + Fragment.LUV = luv+vec2(size.x, size.y); + Fragment.Place = place; gl_Position = ubo.projview*tempVec; EmitVertex(); break; case 3: // xz inv tempVec = baseVec; - tempVec.z += size.y; + tempVec.x += size.x / 16.f; tempVec = ubo.model*tempVec; - fragment.GeoPos = vec3(tempVec); - fragment.VoxMTL = voxMTL; - fragment.LUV = luv+vec2(size.x, 0); + Fragment.GeoPos = tempVec.xyz; + Fragment.VoxMTL = voxMTL; + Fragment.LUV = luv+vec2(size.x, 0); + Fragment.Place = place; gl_Position = ubo.projview*tempVec; EmitVertex(); tempVec = baseVec; - tempVec.x += size.x; + tempVec.z += size.y / 16.f; tempVec = ubo.model*tempVec; - fragment.GeoPos = vec3(tempVec); - fragment.VoxMTL = voxMTL; - fragment.LUV = luv+vec2(0, size.y); + Fragment.GeoPos = tempVec.xyz; + Fragment.VoxMTL = voxMTL; + Fragment.LUV = luv+vec2(0, size.y); + Fragment.Place = place; gl_Position = ubo.projview*tempVec; EmitVertex(); tempVec = baseVec; - tempVec.x += size.x; - tempVec.z += size.y; + tempVec.x += size.x / 16.f; + tempVec.z += size.y / 16.f; tempVec = ubo.model*tempVec; - fragment.GeoPos = vec3(tempVec); - fragment.VoxMTL = voxMTL; - fragment.LUV = luv+vec2(size.x, size.y); + Fragment.GeoPos = tempVec.xyz; + Fragment.VoxMTL = voxMTL; + Fragment.LUV = luv+vec2(size.x, size.y); + Fragment.Place = place; gl_Position = ubo.projview*tempVec; EmitVertex(); break; case 4: // xy inv tempVec = baseVec; - tempVec.y += size.y; + tempVec.y += size.y / 16.f; tempVec = ubo.model*tempVec; - fragment.GeoPos = vec3(tempVec); - fragment.VoxMTL = voxMTL; - fragment.LUV = luv+vec2(size.x, 0); + Fragment.GeoPos = tempVec.xyz; + Fragment.VoxMTL = voxMTL; + Fragment.LUV = luv+vec2(0, size.y); + Fragment.Place = place; gl_Position = ubo.projview*tempVec; EmitVertex(); tempVec = baseVec; - tempVec.x += size.x; + tempVec.x += size.x / 16.f; tempVec = ubo.model*tempVec; - fragment.GeoPos = vec3(tempVec); - fragment.VoxMTL = voxMTL; - fragment.LUV = luv+vec2(0, size.y); + Fragment.GeoPos = tempVec.xyz; + Fragment.VoxMTL = voxMTL; + Fragment.LUV = luv+vec2(size.x, 0); + Fragment.Place = place; gl_Position = ubo.projview*tempVec; EmitVertex(); tempVec = baseVec; - tempVec.x += size.x; - tempVec.y += size.y; + tempVec.x += size.x / 16.f; + tempVec.y += size.y / 16.f; tempVec = ubo.model*tempVec; - fragment.GeoPos = vec3(tempVec); - fragment.VoxMTL = voxMTL; - fragment.LUV = luv+vec2(size.x, size.y); + Fragment.GeoPos = tempVec.xyz; + Fragment.VoxMTL = voxMTL; + Fragment.LUV = luv+vec2(size.x, size.y); + Fragment.Place = place; gl_Position = ubo.projview*tempVec; EmitVertex(); break; case 5: // zy inv tempVec = baseVec; - tempVec.y += size.y; + tempVec.z += size.x / 16.f; tempVec = ubo.model*tempVec; - fragment.GeoPos = vec3(tempVec); - fragment.VoxMTL = voxMTL; - fragment.LUV = luv+vec2(size.x, 0); + Fragment.GeoPos = tempVec.xyz; + Fragment.VoxMTL = voxMTL; + Fragment.LUV = luv+vec2(size.x, 0); + Fragment.Place = place; gl_Position = ubo.projview*tempVec; EmitVertex(); tempVec = baseVec; - tempVec.z += size.x; + tempVec.y += size.y / 16.f; tempVec = ubo.model*tempVec; - fragment.GeoPos = vec3(tempVec); - fragment.VoxMTL = voxMTL; - fragment.LUV = luv+vec2(0, size.y); + Fragment.GeoPos = tempVec.xyz; + Fragment.VoxMTL = voxMTL; + Fragment.LUV = luv+vec2(0, size.y); + Fragment.Place = place; gl_Position = ubo.projview*tempVec; EmitVertex(); tempVec = baseVec; - tempVec.z += size.x; - tempVec.y += size.y; + tempVec.z += size.x / 16.f; + tempVec.y += size.y / 16.f; tempVec = ubo.model*tempVec; - fragment.GeoPos = vec3(tempVec); - fragment.VoxMTL = voxMTL; - fragment.LUV = luv+vec2(size.x, size.y); + Fragment.GeoPos = tempVec.xyz; + Fragment.VoxMTL = voxMTL; + Fragment.LUV = luv+vec2(size.x, size.y); + Fragment.Place = place; gl_Position = ubo.projview*tempVec; EmitVertex(); diff --git a/assets/shaders/chunk/voxel.geom.bin b/assets/shaders/chunk/voxel.geom.bin index 1aa3a6e..a6472dc 100644 Binary files a/assets/shaders/chunk/voxel.geom.bin and b/assets/shaders/chunk/voxel.geom.bin differ diff --git a/assets/shaders/chunk/voxel_opaque.frag b/assets/shaders/chunk/voxel_opaque.frag index cfd6b29..658860e 100644 --- a/assets/shaders/chunk/voxel_opaque.frag +++ b/assets/shaders/chunk/voxel_opaque.frag @@ -2,8 +2,9 @@ layout(location = 0) in FragmentObj { vec3 GeoPos; // Реальная позиция в мире - uint VoxMTL; // Материал вокселя + flat uint VoxMTL; // Материал вокселя vec2 LUV; + flat uint Place; } Fragment; layout(location = 0) out vec4 Frame; @@ -73,5 +74,24 @@ vec4 atlasColor(uint texId, vec2 uv) } void main() { - Frame = atlasColor(0, Fragment.GeoPos.xy); + vec2 uv; + + switch(Fragment.Place) { + case 0: + uv = Fragment.GeoPos.xz; break; + case 1: + uv = Fragment.GeoPos.xy; break; + case 2: + uv = Fragment.GeoPos.zy; break; + case 3: + uv = Fragment.GeoPos.xz*vec2(-1, -1); break; + case 4: + uv = Fragment.GeoPos.xy*vec2(-1, 1); break; + case 5: + uv = Fragment.GeoPos.zy*vec2(-1, 1); break; + default: + uv = vec2(0); + } + + Frame = atlasColor(Fragment.VoxMTL, uv); } diff --git a/assets/shaders/chunk/voxel_opaque.frag.bin b/assets/shaders/chunk/voxel_opaque.frag.bin index d6120de..e132247 100644 Binary files a/assets/shaders/chunk/voxel_opaque.frag.bin and b/assets/shaders/chunk/voxel_opaque.frag.bin differ diff --git a/assets/shaders/chunk/voxel_transparent.frag b/assets/shaders/chunk/voxel_transparent.frag index b1a6436..d5aa336 100644 --- a/assets/shaders/chunk/voxel_transparent.frag +++ b/assets/shaders/chunk/voxel_transparent.frag @@ -4,6 +4,7 @@ layout(location = 0) in Fragment { vec3 GeoPos; // Реальная позиция в мире uint VoxMTL; // Материал вокселя vec2 LUV; + uint Place; } fragment; layout(location = 0) out vec4 Frame; diff --git a/assets/shaders/chunk/voxel_transparent.frag.bin b/assets/shaders/chunk/voxel_transparent.frag.bin index 61a69b1..bb40206 100644 Binary files a/assets/shaders/chunk/voxel_transparent.frag.bin and b/assets/shaders/chunk/voxel_transparent.frag.bin differ diff --git a/assets/grass.png b/assets/textures/grass.png similarity index 100% rename from assets/grass.png rename to assets/textures/grass.png diff --git a/assets/textures/tropical_rainforest_wood.png b/assets/textures/tropical_rainforest_wood.png new file mode 100644 index 0000000..6107044 Binary files /dev/null and b/assets/textures/tropical_rainforest_wood.png differ diff --git a/assets/textures/willow_wood.png b/assets/textures/willow_wood.png new file mode 100644 index 0000000..af57e11 Binary files /dev/null and b/assets/textures/willow_wood.png differ diff --git a/assets/textures/xnether_blue_wood.png b/assets/textures/xnether_blue_wood.png new file mode 100644 index 0000000..6a989b8 Binary files /dev/null and b/assets/textures/xnether_blue_wood.png differ diff --git a/assets/textures/xnether_purple_wood.png b/assets/textures/xnether_purple_wood.png new file mode 100644 index 0000000..cd66800 Binary files /dev/null and b/assets/textures/xnether_purple_wood.png differ