553 const std::string &textureManifestPath,
554 const std::string &textureBasePath,
555 const std::vector<char> &vertSpv,
556 const std::vector<char> &fragSpv) {
557 if (targetWindow ==
nullptr) {
558 throw mxvk::Exception(
"walk: raw pillar renderer requires a valid window");
560 window = targetWindow;
562 fragmentSpv = fragSpv;
564 if (!window->ensureRenderResources()) {
565 throw mxvk::Exception(
"walk: raw pillar renderer requires render resources");
569 loadTexture(textureManifestPath, textureBasePath);
570 createTextureSampler();
571 createDescriptorSetLayout();
572 createUniformBuffers();
573 createDescriptorPool();
574 createDescriptorSets();
579 if (targetWindow ==
nullptr || targetWindow->
getDevice() == VK_NULL_HANDLE) {
583 window = targetWindow;
585 destroyDescriptors();
586 createDescriptorSetLayout();
587 createUniformBuffers();
588 createDescriptorPool();
589 createDescriptorSets();
596 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE || newFragSpv.empty()) {
599 vkDeviceWaitIdle(window->getDevice());
600 fragmentSpv = newFragSpv;
606 if (targetWindow ==
nullptr || targetWindow->
getDevice() == VK_NULL_HANDLE) {
610 window = targetWindow;
612 destroyDescriptors();
620 const std::vector<PillarInstance> &pillars,
621 const glm::mat4 &view,
622 const glm::mat4 &proj,
623 const glm::vec4 &fx) {
624 if (cmd == VK_NULL_HANDLE || pipeline == VK_NULL_HANDLE || pipelineLayout == VK_NULL_HANDLE) {
627 if (imageIndex >= uniformBuffersMapped.size() || descriptorSets.empty() || vertexBuffer == VK_NULL_HANDLE || indexBuffer == VK_NULL_HANDLE) {
632 uniforms.
view = view;
633 uniforms.
proj = proj;
635 std::memcpy(uniformBuffersMapped[imageIndex], &uniforms,
sizeof(
PillarUniforms));
637 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
638 vkCmdBindDescriptorSets(cmd,
639 VK_PIPELINE_BIND_POINT_GRAPHICS,
643 &descriptorSets[imageIndex],
647 const VkDeviceSize offset = 0;
648 vkCmdBindVertexBuffers(cmd, 0, 1, &vertexBuffer, &offset);
649 vkCmdBindIndexBuffer(cmd, indexBuffer, 0, VK_INDEX_TYPE_UINT32);
655 constexpr float baseSink = 0.02f;
656 glm::mat4 model = glm::translate(glm::mat4(1.0f), glm::vec3(pillar.position.x, pillar.position.y - baseSink, pillar.position.z));
657 model = glm::scale(model, glm::vec3(pillar.radius, pillar.height, pillar.radius));
658 vkCmdPushConstants(cmd, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0,
sizeof(glm::mat4), &model);
659 vkCmdDrawIndexed(cmd, indexCount, 1, 0, 0, 0);
664 [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
const {
665 VkPhysicalDeviceMemoryProperties memProperties{};
666 vkGetPhysicalDeviceMemoryProperties(window->getPhysicalDevice(), &memProperties);
667 for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) {
668 if ((typeFilter & (1u << i)) != 0u && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
672 throw mxvk::Exception(
"walk: failed to find suitable memory type for raw pillar renderer");
675 void createBuffer(VkDeviceSize size,
676 VkBufferUsageFlags usage,
677 VkMemoryPropertyFlags properties,
679 VkDeviceMemory &bufferMemory)
const {
680 VkBufferCreateInfo bufferInfo{};
681 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
682 bufferInfo.size = size;
683 bufferInfo.usage = usage;
684 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
686 if (vkCreateBuffer(window->getDevice(), &bufferInfo,
nullptr, &buffer) != VK_SUCCESS) {
687 throw mxvk::Exception(
"walk: failed to create raw pillar buffer");
690 VkMemoryRequirements requirements{};
691 vkGetBufferMemoryRequirements(window->getDevice(), buffer, &requirements);
693 VkMemoryAllocateInfo allocInfo{};
694 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
695 allocInfo.allocationSize = requirements.size;
698 allocInfo.memoryTypeIndex = findMemoryType(requirements.memoryTypeBits, properties);
699 if (vkAllocateMemory(window->getDevice(), &allocInfo,
nullptr, &bufferMemory) != VK_SUCCESS) {
700 throw mxvk::Exception(
"walk: failed to allocate raw pillar buffer memory");
703 if (vkBindBufferMemory(window->getDevice(), buffer, bufferMemory, 0) != VK_SUCCESS) {
704 throw mxvk::Exception(
"walk: failed to bind raw pillar buffer memory");
707 if (bufferMemory != VK_NULL_HANDLE) {
708 vkFreeMemory(window->getDevice(), bufferMemory,
nullptr);
709 bufferMemory = VK_NULL_HANDLE;
711 if (buffer != VK_NULL_HANDLE) {
712 vkDestroyBuffer(window->getDevice(), buffer,
nullptr);
713 buffer = VK_NULL_HANDLE;
719 void createImage(uint32_t width,
722 VkImageTiling tiling,
723 VkImageUsageFlags usage,
724 VkMemoryPropertyFlags properties,
726 VkDeviceMemory &memory)
const {
727 VkImageCreateInfo imageInfo{};
728 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
729 imageInfo.imageType = VK_IMAGE_TYPE_2D;
730 imageInfo.extent.width = width;
731 imageInfo.extent.height = height;
732 imageInfo.extent.depth = 1;
733 imageInfo.mipLevels = 1;
734 imageInfo.arrayLayers = 1;
735 imageInfo.format = format;
736 imageInfo.tiling = tiling;
737 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
738 imageInfo.usage = usage;
739 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
740 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
742 if (vkCreateImage(window->getDevice(), &imageInfo,
nullptr, &image) != VK_SUCCESS) {
743 throw mxvk::Exception(
"walk: failed to create raw pillar image");
746 VkMemoryRequirements requirements{};
747 vkGetImageMemoryRequirements(window->getDevice(), image, &requirements);
749 VkMemoryAllocateInfo allocInfo{};
750 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
751 allocInfo.allocationSize = requirements.size;
754 allocInfo.memoryTypeIndex = findMemoryType(requirements.memoryTypeBits, properties);
755 if (vkAllocateMemory(window->getDevice(), &allocInfo,
nullptr, &memory) != VK_SUCCESS) {
756 throw mxvk::Exception(
"walk: failed to allocate raw pillar image memory");
759 if (vkBindImageMemory(window->getDevice(), image, memory, 0) != VK_SUCCESS) {
760 throw mxvk::Exception(
"walk: failed to bind raw pillar image memory");
763 if (memory != VK_NULL_HANDLE) {
764 vkFreeMemory(window->getDevice(), memory,
nullptr);
765 memory = VK_NULL_HANDLE;
767 if (image != VK_NULL_HANDLE) {
768 vkDestroyImage(window->getDevice(), image,
nullptr);
769 image = VK_NULL_HANDLE;
775 VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags)
const {
776 VkImageViewCreateInfo viewInfo{};
777 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
778 viewInfo.image = image;
779 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
780 viewInfo.format = format;
781 viewInfo.subresourceRange.aspectMask = aspectFlags;
782 viewInfo.subresourceRange.baseMipLevel = 0;
783 viewInfo.subresourceRange.levelCount = 1;
784 viewInfo.subresourceRange.baseArrayLayer = 0;
785 viewInfo.subresourceRange.layerCount = 1;
787 VkImageView imageView = VK_NULL_HANDLE;
788 if (vkCreateImageView(window->getDevice(), &viewInfo,
nullptr, &imageView) != VK_SUCCESS) {
789 throw mxvk::Exception(
"walk: failed to create raw pillar image view");
794 [[nodiscard]] VkCommandBuffer beginSingleTimeCommands()
const {
795 VkCommandBufferAllocateInfo allocInfo{};
796 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
797 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
798 allocInfo.commandPool = window->getCommandPool();
799 allocInfo.commandBufferCount = 1;
801 VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
802 if (vkAllocateCommandBuffers(window->getDevice(), &allocInfo, &commandBuffer) != VK_SUCCESS) {
803 throw mxvk::Exception(
"walk: failed to allocate raw pillar command buffer");
806 VkCommandBufferBeginInfo beginInfo{};
807 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
808 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
809 if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
810 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
811 throw mxvk::Exception(
"walk: failed to begin raw pillar command buffer");
814 return commandBuffer;
817 void endSingleTimeCommands(VkCommandBuffer commandBuffer)
const {
818 if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
819 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
820 throw mxvk::Exception(
"walk: failed to end raw pillar command buffer");
823 VkSubmitInfo submitInfo{};
824 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
825 submitInfo.commandBufferCount = 1;
826 submitInfo.pCommandBuffers = &commandBuffer;
828 if (vkQueueSubmit(window->getGraphicsQueue(), 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) {
829 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
830 throw mxvk::Exception(
"walk: failed to submit raw pillar command buffer");
832 if (vkQueueWaitIdle(window->getGraphicsQueue()) != VK_SUCCESS) {
833 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
834 throw mxvk::Exception(
"walk: failed to wait for raw pillar upload queue");
837 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
840 void transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout)
const {
841 VkCommandBuffer cmd = beginSingleTimeCommands();
843 VkImageMemoryBarrier barrier{};
844 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
845 barrier.oldLayout = oldLayout;
846 barrier.newLayout = newLayout;
847 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
848 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
849 barrier.image = image;
850 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
851 barrier.subresourceRange.baseMipLevel = 0;
852 barrier.subresourceRange.levelCount = 1;
853 barrier.subresourceRange.baseArrayLayer = 0;
854 barrier.subresourceRange.layerCount = 1;
856 VkPipelineStageFlags sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
857 VkPipelineStageFlags destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
858 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
859 barrier.srcAccessMask = 0;
860 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
861 }
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
862 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
863 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
864 sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
865 destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
868 vkCmdPipelineBarrier(cmd, sourceStage, destinationStage, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
869 endSingleTimeCommands(cmd);
872 void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height)
const {
873 VkCommandBuffer cmd = beginSingleTimeCommands();
874 VkBufferImageCopy region{};
875 region.bufferOffset = 0;
876 region.bufferRowLength = 0;
877 region.bufferImageHeight = 0;
878 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
879 region.imageSubresource.mipLevel = 0;
880 region.imageSubresource.baseArrayLayer = 0;
881 region.imageSubresource.layerCount = 1;
882 region.imageOffset = {0, 0, 0};
883 region.imageExtent = {width, height, 1};
885 vkCmdCopyBufferToImage(cmd, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
886 endSingleTimeCommands(cmd);
889 void createTextureSampler() {
890 if (textureSampler != VK_NULL_HANDLE) {
894 VkPhysicalDeviceFeatures deviceFeatures{};
895 vkGetPhysicalDeviceFeatures(window->getPhysicalDevice(), &deviceFeatures);
896 VkPhysicalDeviceProperties deviceProperties{};
897 vkGetPhysicalDeviceProperties(window->getPhysicalDevice(), &deviceProperties);
898 const bool anisotropySupported = deviceFeatures.samplerAnisotropy == VK_TRUE;
899 const float anisotropyLevel = anisotropySupported
900 ? std::min(8.0f, deviceProperties.limits.maxSamplerAnisotropy)
903 VkSamplerCreateInfo samplerInfo{};
904 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
905 samplerInfo.magFilter = VK_FILTER_LINEAR;
906 samplerInfo.minFilter = VK_FILTER_LINEAR;
907 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
908 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
909 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
910 samplerInfo.anisotropyEnable = anisotropySupported ? VK_TRUE : VK_FALSE;
911 samplerInfo.maxAnisotropy = anisotropyLevel;
912 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
913 samplerInfo.unnormalizedCoordinates = VK_FALSE;
914 samplerInfo.compareEnable = VK_FALSE;
915 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
916 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
918 if (vkCreateSampler(window->getDevice(), &samplerInfo,
nullptr, &textureSampler) != VK_SUCCESS) {
919 throw mxvk::Exception(
"walk: failed to create raw pillar texture sampler");
923 void createDescriptorSetLayout() {
924 if (descriptorSetLayout != VK_NULL_HANDLE) {
928 VkDescriptorSetLayoutBinding samplerBinding{};
929 samplerBinding.binding = 0;
930 samplerBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
931 samplerBinding.descriptorCount = 1;
932 samplerBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
934 VkDescriptorSetLayoutBinding uboBinding{};
935 uboBinding.binding = 1;
936 uboBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
937 uboBinding.descriptorCount = 1;
938 uboBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
940 const std::array<VkDescriptorSetLayoutBinding, 2> bindings = {samplerBinding, uboBinding};
942 VkDescriptorSetLayoutCreateInfo layoutInfo{};
943 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
944 layoutInfo.bindingCount =
static_cast<uint32_t
>(bindings.size());
945 layoutInfo.pBindings = bindings.data();
947 if (vkCreateDescriptorSetLayout(window->getDevice(), &layoutInfo,
nullptr, &descriptorSetLayout) != VK_SUCCESS) {
948 throw mxvk::Exception(
"walk: failed to create raw pillar descriptor set layout");
952 void createUniformBuffers() {
953 destroyUniformBuffers();
955 const size_t frameCount = window->getSwapchainImageCount();
956 if (frameCount == 0) {
960 uniformBuffers.resize(frameCount, VK_NULL_HANDLE);
961 uniformBufferMemory.resize(frameCount, VK_NULL_HANDLE);
962 uniformBuffersMapped.resize(frameCount,
nullptr);
964 for (
size_t i = 0; i < frameCount; ++i) {
965 createBuffer(
sizeof(PillarUniforms),
966 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
967 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
969 uniformBufferMemory[i]);
970 vkMapMemory(window->getDevice(), uniformBufferMemory[i], 0,
sizeof(PillarUniforms), 0, &uniformBuffersMapped[i]);
974 void createDescriptorPool() {
975 const uint32_t frameCount =
static_cast<uint32_t
>(window->getSwapchainImageCount());
976 std::array<VkDescriptorPoolSize, 2> poolSizes{};
977 poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
978 poolSizes[0].descriptorCount = frameCount;
979 poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
980 poolSizes[1].descriptorCount = frameCount;
982 VkDescriptorPoolCreateInfo poolInfo{};
983 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
984 poolInfo.poolSizeCount =
static_cast<uint32_t
>(poolSizes.size());
985 poolInfo.pPoolSizes = poolSizes.data();
986 poolInfo.maxSets = frameCount;
988 if (vkCreateDescriptorPool(window->getDevice(), &poolInfo,
nullptr, &descriptorPool) != VK_SUCCESS) {
989 throw mxvk::Exception(
"walk: failed to create raw pillar descriptor pool");
993 void createDescriptorSets() {
994 const size_t frameCount = window->getSwapchainImageCount();
995 std::vector<VkDescriptorSetLayout> layouts(frameCount, descriptorSetLayout);
997 VkDescriptorSetAllocateInfo allocInfo{};
998 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
999 allocInfo.descriptorPool = descriptorPool;
1000 allocInfo.descriptorSetCount =
static_cast<uint32_t
>(frameCount);
1001 allocInfo.pSetLayouts = layouts.data();
1003 descriptorSets.resize(frameCount, VK_NULL_HANDLE);
1004 if (vkAllocateDescriptorSets(window->getDevice(), &allocInfo, descriptorSets.data()) != VK_SUCCESS) {
1005 throw mxvk::Exception(
"walk: failed to allocate raw pillar descriptor sets");
1008 VkDescriptorImageInfo imageInfo{};
1009 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1010 imageInfo.imageView = textureView;
1011 imageInfo.sampler = textureSampler;
1013 for (
size_t i = 0; i < frameCount; ++i) {
1014 VkDescriptorBufferInfo bufferInfo{};
1015 bufferInfo.buffer = uniformBuffers[i];
1016 bufferInfo.offset = 0;
1017 bufferInfo.range =
sizeof(PillarUniforms);
1019 std::array<VkWriteDescriptorSet, 2> writes{};
1020 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1021 writes[0].dstSet = descriptorSets[i];
1022 writes[0].dstBinding = 0;
1023 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1024 writes[0].descriptorCount = 1;
1025 writes[0].pImageInfo = &imageInfo;
1027 writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1028 writes[1].dstSet = descriptorSets[i];
1029 writes[1].dstBinding = 1;
1030 writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1031 writes[1].descriptorCount = 1;
1032 writes[1].pBufferInfo = &bufferInfo;
1034 vkUpdateDescriptorSets(window->getDevice(),
static_cast<uint32_t
>(writes.size()), writes.data(), 0,
nullptr);
1038 void createPipeline() {
1039 if (descriptorSetLayout == VK_NULL_HANDLE || vertexSpv.empty() || fragmentSpv.empty() || window->getSwapchainFormat() == VK_FORMAT_UNDEFINED) {
1046 VkPipelineShaderStageCreateInfo vertStage{};
1047 vertStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1048 vertStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
1049 vertStage.module = vertModule;
1050 vertStage.pName =
"main";
1052 VkPipelineShaderStageCreateInfo fragStage{};
1053 fragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1054 fragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
1055 fragStage.module = fragModule;
1056 fragStage.pName =
"main";
1057 const std::array<VkPipelineShaderStageCreateInfo, 2> stages = {vertStage, fragStage};
1059 VkVertexInputBindingDescription binding{};
1060 binding.binding = 0;
1061 binding.stride =
sizeof(PillarVertex);
1062 binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1064 std::array<VkVertexInputAttributeDescription, 3> attrs{};
1065 attrs[0] = {0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(PillarVertex, position)};
1066 attrs[1] = {1, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(PillarVertex, texCoord)};
1067 attrs[2] = {2, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(PillarVertex, normal)};
1069 VkPipelineVertexInputStateCreateInfo vertexInput{};
1070 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
1071 vertexInput.vertexBindingDescriptionCount = 1;
1072 vertexInput.pVertexBindingDescriptions = &binding;
1073 vertexInput.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attrs.size());
1074 vertexInput.pVertexAttributeDescriptions = attrs.data();
1076 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
1077 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
1078 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
1080 const std::array<VkDynamicState, 2> dynamicStates = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
1081 VkPipelineDynamicStateCreateInfo dynamicInfo{};
1082 dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
1083 dynamicInfo.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
1084 dynamicInfo.pDynamicStates = dynamicStates.data();
1086 VkPipelineViewportStateCreateInfo viewportState{};
1087 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1088 viewportState.viewportCount = 1;
1089 viewportState.scissorCount = 1;
1091 VkPipelineRasterizationStateCreateInfo rasterizer{};
1092 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
1093 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
1096 rasterizer.cullMode = VK_CULL_MODE_NONE;
1097 rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
1098 rasterizer.lineWidth = 1.0f;
1099 rasterizer.depthBiasEnable = VK_FALSE;
1101 VkPipelineMultisampleStateCreateInfo multisample{};
1102 multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
1103 multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
1105 VkPipelineDepthStencilStateCreateInfo depthStencil{};
1106 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
1107 depthStencil.depthTestEnable = VK_TRUE;
1108 depthStencil.depthWriteEnable = VK_TRUE;
1109 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
1111 VkPipelineColorBlendAttachmentState blendAttachment{};
1112 blendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
1113 blendAttachment.blendEnable = VK_FALSE;
1115 VkPipelineColorBlendStateCreateInfo colorBlend{};
1116 colorBlend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
1117 colorBlend.attachmentCount = 1;
1118 colorBlend.pAttachments = &blendAttachment;
1120 VkPushConstantRange pushRange{};
1121 pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
1122 pushRange.offset = 0;
1123 pushRange.size =
sizeof(glm::mat4);
1125 VkPipelineLayoutCreateInfo layoutInfo{};
1126 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
1127 layoutInfo.setLayoutCount = 1;
1128 layoutInfo.pSetLayouts = &descriptorSetLayout;
1129 layoutInfo.pushConstantRangeCount = 1;
1130 layoutInfo.pPushConstantRanges = &pushRange;
1132 if (vkCreatePipelineLayout(window->getDevice(), &layoutInfo,
nullptr, &pipelineLayout) != VK_SUCCESS) {
1133 vkDestroyShaderModule(window->getDevice(), fragModule,
nullptr);
1134 vkDestroyShaderModule(window->getDevice(), vertModule,
nullptr);
1135 throw mxvk::Exception(
"walk: failed to create raw pillar pipeline layout");
1138 const VkFormat colorFormat = window->getSwapchainFormat();
1139 const VkFormat depthFormat = window->getDepthFormat();
1140 VkPipelineRenderingCreateInfo renderingInfo{};
1141 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
1142 renderingInfo.colorAttachmentCount = 1;
1143 renderingInfo.pColorAttachmentFormats = &colorFormat;
1144 if (depthFormat != VK_FORMAT_UNDEFINED) {
1145 renderingInfo.depthAttachmentFormat = depthFormat;
1148 VkGraphicsPipelineCreateInfo pipelineInfo{};
1149 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
1150 pipelineInfo.pNext = &renderingInfo;
1151 pipelineInfo.stageCount =
static_cast<uint32_t
>(stages.size());
1152 pipelineInfo.pStages = stages.data();
1153 pipelineInfo.pVertexInputState = &vertexInput;
1154 pipelineInfo.pInputAssemblyState = &inputAssembly;
1155 pipelineInfo.pViewportState = &viewportState;
1156 pipelineInfo.pRasterizationState = &rasterizer;
1157 pipelineInfo.pMultisampleState = &multisample;
1158 pipelineInfo.pDepthStencilState = &depthStencil;
1159 pipelineInfo.pColorBlendState = &colorBlend;
1160 pipelineInfo.pDynamicState = &dynamicInfo;
1161 pipelineInfo.layout = pipelineLayout;
1162 pipelineInfo.renderPass = VK_NULL_HANDLE;
1164 if (vkCreateGraphicsPipelines(window->getDevice(), VK_NULL_HANDLE, 1, &pipelineInfo,
nullptr, &pipeline) != VK_SUCCESS) {
1165 vkDestroyPipelineLayout(window->getDevice(), pipelineLayout,
nullptr);
1166 pipelineLayout = VK_NULL_HANDLE;
1167 vkDestroyShaderModule(window->getDevice(), fragModule,
nullptr);
1168 vkDestroyShaderModule(window->getDevice(), vertModule,
nullptr);
1169 throw mxvk::Exception(
"walk: failed to create raw pillar graphics pipeline");
1172 vkDestroyShaderModule(window->getDevice(), fragModule,
nullptr);
1173 vkDestroyShaderModule(window->getDevice(), vertModule,
nullptr);
1176 void destroyPipeline() {
1177 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
1178 pipeline = VK_NULL_HANDLE;
1179 pipelineLayout = VK_NULL_HANDLE;
1183 if (pipeline != VK_NULL_HANDLE) {
1184 vkDestroyPipeline(window->getDevice(), pipeline,
nullptr);
1185 pipeline = VK_NULL_HANDLE;
1187 if (pipelineLayout != VK_NULL_HANDLE) {
1188 vkDestroyPipelineLayout(window->getDevice(), pipelineLayout,
nullptr);
1189 pipelineLayout = VK_NULL_HANDLE;
1193 void destroyDescriptors() {
1194 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
1195 descriptorSets.clear();
1196 descriptorPool = VK_NULL_HANDLE;
1197 descriptorSetLayout = VK_NULL_HANDLE;
1198 destroyUniformBuffers();
1202 descriptorSets.clear();
1203 if (descriptorPool != VK_NULL_HANDLE) {
1204 vkDestroyDescriptorPool(window->getDevice(), descriptorPool,
nullptr);
1205 descriptorPool = VK_NULL_HANDLE;
1207 if (descriptorSetLayout != VK_NULL_HANDLE) {
1208 vkDestroyDescriptorSetLayout(window->getDevice(), descriptorSetLayout,
nullptr);
1209 descriptorSetLayout = VK_NULL_HANDLE;
1211 destroyUniformBuffers();
1214 void destroyUniformBuffers() {
1215 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
1216 uniformBuffers.clear();
1217 uniformBufferMemory.clear();
1218 uniformBuffersMapped.clear();
1222 for (
size_t i = 0; i < uniformBuffers.size(); ++i) {
1223 if (uniformBuffersMapped[i] !=
nullptr) {
1224 vkUnmapMemory(window->getDevice(), uniformBufferMemory[i]);
1225 uniformBuffersMapped[i] =
nullptr;
1227 if (uniformBuffers[i] != VK_NULL_HANDLE) {
1228 vkDestroyBuffer(window->getDevice(), uniformBuffers[i],
nullptr);
1230 if (uniformBufferMemory[i] != VK_NULL_HANDLE) {
1231 vkFreeMemory(window->getDevice(), uniformBufferMemory[i],
nullptr);
1235 uniformBuffers.clear();
1236 uniformBufferMemory.clear();
1237 uniformBuffersMapped.clear();
1240 void destroyTexture() {
1241 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
1242 textureView = VK_NULL_HANDLE;
1243 textureImage = VK_NULL_HANDLE;
1244 textureMemory = VK_NULL_HANDLE;
1245 textureSampler = VK_NULL_HANDLE;
1249 if (textureView != VK_NULL_HANDLE) {
1250 vkDestroyImageView(window->getDevice(), textureView,
nullptr);
1251 textureView = VK_NULL_HANDLE;
1253 if (textureImage != VK_NULL_HANDLE) {
1254 vkDestroyImage(window->getDevice(), textureImage,
nullptr);
1255 textureImage = VK_NULL_HANDLE;
1257 if (textureMemory != VK_NULL_HANDLE) {
1258 vkFreeMemory(window->getDevice(), textureMemory,
nullptr);
1259 textureMemory = VK_NULL_HANDLE;
1261 if (textureSampler != VK_NULL_HANDLE) {
1262 vkDestroySampler(window->getDevice(), textureSampler,
nullptr);
1263 textureSampler = VK_NULL_HANDLE;
1267 void destroyBuffers() {
1268 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
1269 vertexBuffer = VK_NULL_HANDLE;
1270 vertexMemory = VK_NULL_HANDLE;
1271 indexBuffer = VK_NULL_HANDLE;
1272 indexMemory = VK_NULL_HANDLE;
1276 if (vertexBuffer != VK_NULL_HANDLE) {
1277 vkDestroyBuffer(window->getDevice(), vertexBuffer,
nullptr);
1278 vertexBuffer = VK_NULL_HANDLE;
1280 if (vertexMemory != VK_NULL_HANDLE) {
1281 vkFreeMemory(window->getDevice(), vertexMemory,
nullptr);
1282 vertexMemory = VK_NULL_HANDLE;
1284 if (indexBuffer != VK_NULL_HANDLE) {
1285 vkDestroyBuffer(window->getDevice(), indexBuffer,
nullptr);
1286 indexBuffer = VK_NULL_HANDLE;
1288 if (indexMemory != VK_NULL_HANDLE) {
1289 vkFreeMemory(window->getDevice(), indexMemory,
nullptr);
1290 indexMemory = VK_NULL_HANDLE;
1294 void buildGeometry() {
1295 constexpr int segments = 16;
1296 constexpr float bottomCapScale = 1.5f;
1297 constexpr float baseDepth = -0.05f;
1299 std::vector<float> vertices;
1300 std::vector<uint32_t> indices;
1301 vertices.reserve(128 * 8);
1302 indices.reserve(192);
1304 for (
int i = 0; i <= segments; ++i) {
1305 const float angle =
static_cast<float>(i) /
static_cast<float>(segments) * 2.0f * 3.14159265358979323846f;
1306 const float xBottom = std::cos(angle) * bottomCapScale;
1307 const float zBottom = std::sin(angle) * bottomCapScale;
1308 const float xTop = std::cos(angle);
1309 const float zTop = std::sin(angle);
1310 const float u =
static_cast<float>(i) /
static_cast<float>(segments);
1311 vertices.insert(vertices.end(), {
1321 vertices.insert(vertices.end(), {
1333 for (
int i = 0; i < segments; ++i) {
1334 const int current = i * 2;
1335 const int next = (i + 1) * 2;
1336 indices.insert(indices.end(), {
1337 static_cast<uint32_t>(current),
1338 static_cast<uint32_t>(current + 1),
1339 static_cast<uint32_t>(next),
1340 static_cast<uint32_t>(next),
1341 static_cast<uint32_t>(current + 1),
1342 static_cast<uint32_t>(next + 1),
1346 const uint32_t bottomCenterIndex =
static_cast<uint32_t
>(vertices.size() / 8);
1347 vertices.insert(vertices.end(), {
1358 const uint32_t bottomCapStart =
static_cast<uint32_t
>(vertices.size() / 8);
1359 for (
int i = 0; i <= segments; ++i) {
1360 const float angle =
static_cast<float>(i) /
static_cast<float>(segments) * 2.0f * 3.14159265358979323846f;
1361 const float x = std::cos(angle) * bottomCapScale;
1362 const float z = std::sin(angle) * bottomCapScale;
1363 vertices.insert(vertices.end(), {
1367 0.5f + x * 0.5f / bottomCapScale,
1368 0.5f + z * 0.5f / bottomCapScale,
1374 for (
int i = 0; i < segments; ++i) {
1375 indices.insert(indices.end(), {
1377 bottomCapStart + static_cast<uint32_t>(i + 1),
1378 bottomCapStart + static_cast<uint32_t>(i),
1382 const uint32_t topCenterIndex =
static_cast<uint32_t
>(vertices.size() / 8);
1383 vertices.insert(vertices.end(), {
1394 const uint32_t topCapStart =
static_cast<uint32_t
>(vertices.size() / 8);
1395 for (
int i = 0; i <= segments; ++i) {
1396 const float angle =
static_cast<float>(i) /
static_cast<float>(segments) * 2.0f * 3.14159265358979323846f;
1397 const float x = std::cos(angle);
1398 const float z = std::sin(angle);
1399 vertices.insert(vertices.end(), {
1410 for (
int i = 0; i < segments; ++i) {
1411 indices.insert(indices.end(), {
1413 topCapStart + static_cast<uint32_t>(i),
1414 topCapStart + static_cast<uint32_t>(i + 1),
1418 vertexCount =
static_cast<uint32_t
>(vertices.size() / 8);
1419 indexCount =
static_cast<uint32_t
>(indices.size());
1421 std::vector<PillarVertex> pillarVertices(vertexCount);
1422 for (uint32_t i = 0; i < vertexCount; ++i) {
1423 const size_t base =
static_cast<size_t>(i) * 8;
1424 pillarVertices[i].position = glm::vec3(vertices[base + 0], vertices[base + 1], vertices[base + 2]);
1425 pillarVertices[i].texCoord = glm::vec2(vertices[base + 3], vertices[base + 4]);
1426 pillarVertices[i].normal = glm::vec3(vertices[base + 5], vertices[base + 6], vertices[base + 7]);
1429 createBuffer(
static_cast<VkDeviceSize
>(pillarVertices.size() *
sizeof(PillarVertex)),
1430 VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
1431 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1434 void *mapped =
nullptr;
1435 vkMapMemory(window->getDevice(), vertexMemory, 0, VK_WHOLE_SIZE, 0, &mapped);
1436 std::memcpy(mapped, pillarVertices.data(), pillarVertices.size() *
sizeof(PillarVertex));
1437 vkUnmapMemory(window->getDevice(), vertexMemory);
1439 createBuffer(
static_cast<VkDeviceSize
>(indices.size() *
sizeof(uint32_t)),
1440 VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
1441 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1444 vkMapMemory(window->getDevice(), indexMemory, 0, VK_WHOLE_SIZE, 0, &mapped);
1445 std::memcpy(mapped, indices.data(), indices.size() *
sizeof(uint32_t));
1446 vkUnmapMemory(window->getDevice(), indexMemory);
1449 void loadTexture([[maybe_unused]]
const std::string &textureManifestPath,
const std::string &textureBasePath) {
1450 SDL_Surface *surface =
mxvk::LoadPNG((textureBasePath +
"/ground.png").c_str());
1451 if (surface ==
nullptr) {
1452 throw mxvk::Exception(
"walk: failed to load raw pillar texture");
1455 const uint32_t width =
static_cast<uint32_t
>(surface->w);
1456 const uint32_t height =
static_cast<uint32_t
>(surface->h);
1457 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(width) *
static_cast<VkDeviceSize
>(height) * 4U;
1459 VkBuffer stagingBuffer = VK_NULL_HANDLE;
1460 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
1461 createBuffer(imageSize,
1462 VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
1463 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1467 void *mapped =
nullptr;
1468 vkMapMemory(window->getDevice(), stagingMemory, 0, imageSize, 0, &mapped);
1469 std::memcpy(mapped, surface->pixels,
static_cast<size_t>(imageSize));
1470 vkUnmapMemory(window->getDevice(), stagingMemory);
1472 createImage(width, height,
1473 VK_FORMAT_R8G8B8A8_UNORM,
1474 VK_IMAGE_TILING_OPTIMAL,
1475 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
1476 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
1480 transitionImageLayout(textureImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
1481 copyBufferToImage(stagingBuffer, textureImage, width, height);
1482 transitionImageLayout(textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
1484 textureView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
1486 vkDestroyBuffer(window->getDevice(), stagingBuffer,
nullptr);
1487 vkFreeMemory(window->getDevice(), stagingMemory,
nullptr);
1488 SDL_DestroySurface(surface);
1491 mxvk::VK_Window *window =
nullptr;
1492 std::vector<char> vertexSpv{};
1493 std::vector<char> fragmentSpv{};
1495 uint32_t vertexCount = 0;
1496 uint32_t indexCount = 0;
1497 VkBuffer vertexBuffer = VK_NULL_HANDLE;
1498 VkDeviceMemory vertexMemory = VK_NULL_HANDLE;
1499 VkBuffer indexBuffer = VK_NULL_HANDLE;
1500 VkDeviceMemory indexMemory = VK_NULL_HANDLE;
1502 VkImage textureImage = VK_NULL_HANDLE;
1503 VkDeviceMemory textureMemory = VK_NULL_HANDLE;
1504 VkImageView textureView = VK_NULL_HANDLE;
1505 VkSampler textureSampler = VK_NULL_HANDLE;
1507 VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE;
1508 VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
1509 std::vector<VkDescriptorSet> descriptorSets{};
1511 VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
1512 VkPipeline pipeline = VK_NULL_HANDLE;
1514 std::vector<VkBuffer> uniformBuffers{};
1515 std::vector<VkDeviceMemory> uniformBufferMemory{};
1516 std::vector<void *> uniformBuffersMapped{};
1534 const std::string &textureManifestPath,
1535 const std::string &textureBasePath,
1536 const std::vector<char> &vertexShaderSpv,
1537 const std::vector<char> &fragmentShaderSpv) {
1538 if (targetWindow ==
nullptr) {
1539 throw mxvk::Exception(
"walk: raw wall renderer requires a valid window");
1541 window = targetWindow;
1542 vertSpv = vertexShaderSpv;
1543 fragSpv = fragmentShaderSpv;
1545 if (!window->ensureRenderResources()) {
1546 throw mxvk::Exception(
"walk: raw wall renderer requires render resources");
1550 loadTexture(textureManifestPath, textureBasePath);
1551 createTextureSampler();
1552 createDescriptorSetLayout();
1553 createUniformBuffers();
1554 createDescriptorPool();
1555 createDescriptorSets();
1560 if (targetWindow ==
nullptr || targetWindow->
getDevice() == VK_NULL_HANDLE) {
1564 window = targetWindow;
1566 destroyDescriptors();
1567 createDescriptorSetLayout();
1568 createUniformBuffers();
1569 createDescriptorPool();
1570 createDescriptorSets();
1577 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE || newFragSpv.empty()) {
1580 vkDeviceWaitIdle(window->getDevice());
1581 fragSpv = newFragSpv;
1587 if (targetWindow ==
nullptr || targetWindow->
getDevice() == VK_NULL_HANDLE) {
1591 window = targetWindow;
1593 destroyDescriptors();
1600 uint32_t imageIndex,
1601 const std::vector<WallSegment> &walls,
1602 float wallThickness,
1603 const glm::mat4 &view,
1604 const glm::mat4 &proj,
1605 const glm::vec4 &fx) {
1606 if (cmd == VK_NULL_HANDLE || pipeline == VK_NULL_HANDLE || pipelineLayout == VK_NULL_HANDLE) {
1609 if (imageIndex >= uniformBuffersMapped.size() || descriptorSets.empty() || vertexBuffer == VK_NULL_HANDLE || indexBuffer == VK_NULL_HANDLE) {
1614 uniforms.
view = view;
1615 uniforms.
proj = proj;
1617 std::memcpy(uniformBuffersMapped[imageIndex], &uniforms,
sizeof(
WallUniforms));
1619 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
1620 vkCmdBindDescriptorSets(cmd,
1621 VK_PIPELINE_BIND_POINT_GRAPHICS,
1625 &descriptorSets[imageIndex],
1629 const VkDeviceSize offset = 0;
1630 vkCmdBindVertexBuffers(cmd, 0, 1, &vertexBuffer, &offset);
1631 vkCmdBindIndexBuffer(cmd, indexBuffer, 0, VK_INDEX_TYPE_UINT32);
1633 const float thickness = std::max(0.02f, wallThickness);
1636 const float wallOverlap = thickness * 0.55f;
1638 const glm::vec3 center = (segment.start + segment.end) * 0.5f;
1639 const glm::vec3 span = segment.end - segment.start;
1640 const float length = glm::length(span);
1641 if (length < 0.0001f) {
1646 constexpr float baseSink = 0.01f;
1647 glm::mat4 model = glm::translate(glm::mat4(1.0f), glm::vec3(center.x, -baseSink, center.z));
1648 model = glm::rotate(model, std::atan2(span.z, span.x), glm::vec3(0.0f, 1.0f, 0.0f));
1649 model = glm::scale(model, glm::vec3(length + wallOverlap * 2.0f, segment.height, thickness));
1650 vkCmdPushConstants(cmd, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0,
sizeof(glm::mat4), &model);
1651 vkCmdDrawIndexed(cmd, indexCount, 1, 0, 0, 0);
1656 [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
const {
1657 VkPhysicalDeviceMemoryProperties memProperties{};
1658 vkGetPhysicalDeviceMemoryProperties(window->getPhysicalDevice(), &memProperties);
1659 for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) {
1660 if ((typeFilter & (1u << i)) != 0u && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
1664 throw mxvk::Exception(
"walk: failed to find suitable memory type for raw wall renderer");
1667 void createBuffer(VkDeviceSize size,
1668 VkBufferUsageFlags usage,
1669 VkMemoryPropertyFlags properties,
1671 VkDeviceMemory &bufferMemory)
const {
1672 VkBufferCreateInfo bufferInfo{};
1673 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
1674 bufferInfo.size = size;
1675 bufferInfo.usage = usage;
1676 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
1678 if (vkCreateBuffer(window->getDevice(), &bufferInfo,
nullptr, &buffer) != VK_SUCCESS) {
1679 throw mxvk::Exception(
"walk: failed to create raw wall buffer");
1682 VkMemoryRequirements requirements{};
1683 vkGetBufferMemoryRequirements(window->getDevice(), buffer, &requirements);
1685 VkMemoryAllocateInfo allocInfo{};
1686 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1687 allocInfo.allocationSize = requirements.size;
1690 allocInfo.memoryTypeIndex = findMemoryType(requirements.memoryTypeBits, properties);
1691 if (vkAllocateMemory(window->getDevice(), &allocInfo,
nullptr, &bufferMemory) != VK_SUCCESS) {
1692 throw mxvk::Exception(
"walk: failed to allocate raw wall buffer memory");
1695 if (vkBindBufferMemory(window->getDevice(), buffer, bufferMemory, 0) != VK_SUCCESS) {
1696 throw mxvk::Exception(
"walk: failed to bind raw wall buffer memory");
1699 if (bufferMemory != VK_NULL_HANDLE) {
1700 vkFreeMemory(window->getDevice(), bufferMemory,
nullptr);
1701 bufferMemory = VK_NULL_HANDLE;
1703 if (buffer != VK_NULL_HANDLE) {
1704 vkDestroyBuffer(window->getDevice(), buffer,
nullptr);
1705 buffer = VK_NULL_HANDLE;
1711 void buildGeometry() {
1714 std::vector<WallVertex> verts;
1715 std::vector<uint32_t> inds;
1719 const auto addFace = [&verts, &inds](
const glm::vec3 &v0,
1720 const glm::vec3 &v1,
1721 const glm::vec3 &v2,
1722 const glm::vec3 &v3,
1723 const glm::vec3 &normal) {
1724 const uint32_t base =
static_cast<uint32_t
>(verts.size());
1725 verts.push_back({v0, glm::vec2(0.0f, 0.0f), normal});
1726 verts.push_back({v1, glm::vec2(1.0f, 0.0f), normal});
1727 verts.push_back({v2, glm::vec2(1.0f, 1.0f), normal});
1728 verts.push_back({v3, glm::vec2(0.0f, 1.0f), normal});
1729 inds.insert(inds.end(), {base + 0, base + 1, base + 2, base + 2, base + 3, base + 0});
1732 constexpr float x = 0.5f;
1733 constexpr float z = 0.5f;
1734 constexpr float y0 = 0.0f;
1735 constexpr float y1 = 1.0f;
1737 addFace(glm::vec3(-x, y0, z), glm::vec3(x, y0, z), glm::vec3(x, y1, z), glm::vec3(-x, y1, z), glm::vec3(0.0f, 0.0f, 1.0f));
1738 addFace(glm::vec3(x, y0, -z), glm::vec3(-x, y0, -z), glm::vec3(-x, y1, -z), glm::vec3(x, y1, -z), glm::vec3(0.0f, 0.0f, -1.0f));
1739 addFace(glm::vec3(x, y0, z), glm::vec3(x, y0, -z), glm::vec3(x, y1, -z), glm::vec3(x, y1, z), glm::vec3(1.0f, 0.0f, 0.0f));
1740 addFace(glm::vec3(-x, y0, -z), glm::vec3(-x, y0, z), glm::vec3(-x, y1, z), glm::vec3(-x, y1, -z), glm::vec3(-1.0f, 0.0f, 0.0f));
1741 addFace(glm::vec3(-x, y1, z), glm::vec3(x, y1, z), glm::vec3(x, y1, -z), glm::vec3(-x, y1, -z), glm::vec3(0.0f, 1.0f, 0.0f));
1742 addFace(glm::vec3(-x, y0, -z), glm::vec3(x, y0, -z), glm::vec3(x, y0, z), glm::vec3(-x, y0, z), glm::vec3(0.0f, -1.0f, 0.0f));
1744 vertexCount =
static_cast<uint32_t
>(verts.size());
1745 indexCount =
static_cast<uint32_t
>(inds.size());
1747 createBuffer(
static_cast<VkDeviceSize
>(verts.size() *
sizeof(WallVertex)),
1748 VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
1749 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1752 void *mapped =
nullptr;
1753 vkMapMemory(window->getDevice(), vertexMemory, 0, VK_WHOLE_SIZE, 0, &mapped);
1754 std::memcpy(mapped, verts.data(), verts.size() *
sizeof(WallVertex));
1755 vkUnmapMemory(window->getDevice(), vertexMemory);
1757 createBuffer(
static_cast<VkDeviceSize
>(inds.size() *
sizeof(uint32_t)),
1758 VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
1759 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1762 vkMapMemory(window->getDevice(), indexMemory, 0, VK_WHOLE_SIZE, 0, &mapped);
1763 std::memcpy(mapped, inds.data(), inds.size() *
sizeof(uint32_t));
1764 vkUnmapMemory(window->getDevice(), indexMemory);
1767 void loadTexture([[maybe_unused]]
const std::string &textureManifestPath,
const std::string &textureBasePath) {
1768 SDL_Surface *surface =
mxvk::LoadPNG((textureBasePath +
"/wall_bricks.png").c_str());
1769 if (surface ==
nullptr) {
1770 throw mxvk::Exception(
"walk: failed to load raw wall texture");
1773 const uint32_t width =
static_cast<uint32_t
>(surface->w);
1774 const uint32_t height =
static_cast<uint32_t
>(surface->h);
1775 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(width) *
static_cast<VkDeviceSize
>(height) * 4U;
1777 VkBuffer stagingBuffer = VK_NULL_HANDLE;
1778 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
1779 createBuffer(imageSize,
1780 VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
1781 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1785 void *mapped =
nullptr;
1786 vkMapMemory(window->getDevice(), stagingMemory, 0, imageSize, 0, &mapped);
1787 std::memcpy(mapped, surface->pixels,
static_cast<size_t>(imageSize));
1788 vkUnmapMemory(window->getDevice(), stagingMemory);
1790 createImage(width, height,
1791 VK_FORMAT_R8G8B8A8_UNORM,
1792 VK_IMAGE_TILING_OPTIMAL,
1793 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
1794 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
1798 transitionImageLayout(textureImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
1799 copyBufferToImage(stagingBuffer, textureImage, width, height);
1800 transitionImageLayout(textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
1802 textureView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
1804 vkDestroyBuffer(window->getDevice(), stagingBuffer,
nullptr);
1805 vkFreeMemory(window->getDevice(), stagingMemory,
nullptr);
1806 SDL_DestroySurface(surface);
1809 void createTextureSampler() {
1810 if (textureSampler != VK_NULL_HANDLE) {
1814 VkPhysicalDeviceFeatures deviceFeatures{};
1815 vkGetPhysicalDeviceFeatures(window->getPhysicalDevice(), &deviceFeatures);
1816 VkPhysicalDeviceProperties deviceProperties{};
1817 vkGetPhysicalDeviceProperties(window->getPhysicalDevice(), &deviceProperties);
1818 const bool anisotropySupported = deviceFeatures.samplerAnisotropy == VK_TRUE;
1819 const float anisotropyLevel = anisotropySupported
1820 ? std::min(8.0f, deviceProperties.limits.maxSamplerAnisotropy)
1823 VkSamplerCreateInfo samplerInfo{};
1824 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
1825 samplerInfo.magFilter = VK_FILTER_LINEAR;
1826 samplerInfo.minFilter = VK_FILTER_LINEAR;
1827 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
1828 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
1829 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
1830 samplerInfo.anisotropyEnable = anisotropySupported ? VK_TRUE : VK_FALSE;
1831 samplerInfo.maxAnisotropy = anisotropyLevel;
1832 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
1833 samplerInfo.unnormalizedCoordinates = VK_FALSE;
1834 samplerInfo.compareEnable = VK_FALSE;
1835 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
1836 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
1838 if (vkCreateSampler(window->getDevice(), &samplerInfo,
nullptr, &textureSampler) != VK_SUCCESS) {
1839 throw mxvk::Exception(
"walk: failed to create raw wall texture sampler");
1843 void createDescriptorSetLayout() {
1844 if (descriptorSetLayout != VK_NULL_HANDLE) {
1848 VkDescriptorSetLayoutBinding samplerBinding{};
1849 samplerBinding.binding = 0;
1850 samplerBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1851 samplerBinding.descriptorCount = 1;
1852 samplerBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
1854 VkDescriptorSetLayoutBinding uboBinding{};
1855 uboBinding.binding = 1;
1856 uboBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1857 uboBinding.descriptorCount = 1;
1858 uboBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
1860 const std::array<VkDescriptorSetLayoutBinding, 2> bindings = {samplerBinding, uboBinding};
1862 VkDescriptorSetLayoutCreateInfo layoutInfo{};
1863 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
1864 layoutInfo.bindingCount =
static_cast<uint32_t
>(bindings.size());
1865 layoutInfo.pBindings = bindings.data();
1867 if (vkCreateDescriptorSetLayout(window->getDevice(), &layoutInfo,
nullptr, &descriptorSetLayout) != VK_SUCCESS) {
1868 throw mxvk::Exception(
"walk: failed to create raw wall descriptor set layout");
1872 void createUniformBuffers() {
1873 destroyUniformBuffers();
1875 const size_t frameCount = window->getSwapchainImageCount();
1876 if (frameCount == 0) {
1880 uniformBuffers.resize(frameCount, VK_NULL_HANDLE);
1881 uniformBufferMemory.resize(frameCount, VK_NULL_HANDLE);
1882 uniformBuffersMapped.resize(frameCount,
nullptr);
1884 for (
size_t i = 0; i < frameCount; ++i) {
1885 createBuffer(
sizeof(WallUniforms),
1886 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
1887 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1889 uniformBufferMemory[i]);
1890 vkMapMemory(window->getDevice(), uniformBufferMemory[i], 0,
sizeof(WallUniforms), 0, &uniformBuffersMapped[i]);
1894 void createDescriptorPool() {
1895 const uint32_t frameCount =
static_cast<uint32_t
>(window->getSwapchainImageCount());
1896 std::array<VkDescriptorPoolSize, 2> poolSizes{};
1897 poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1898 poolSizes[0].descriptorCount = frameCount;
1899 poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1900 poolSizes[1].descriptorCount = frameCount;
1902 VkDescriptorPoolCreateInfo poolInfo{};
1903 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
1904 poolInfo.poolSizeCount =
static_cast<uint32_t
>(poolSizes.size());
1905 poolInfo.pPoolSizes = poolSizes.data();
1906 poolInfo.maxSets = frameCount;
1908 if (vkCreateDescriptorPool(window->getDevice(), &poolInfo,
nullptr, &descriptorPool) != VK_SUCCESS) {
1909 throw mxvk::Exception(
"walk: failed to create raw wall descriptor pool");
1913 void createDescriptorSets() {
1914 const size_t frameCount = window->getSwapchainImageCount();
1915 std::vector<VkDescriptorSetLayout> layouts(frameCount, descriptorSetLayout);
1917 VkDescriptorSetAllocateInfo allocInfo{};
1918 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
1919 allocInfo.descriptorPool = descriptorPool;
1920 allocInfo.descriptorSetCount =
static_cast<uint32_t
>(frameCount);
1921 allocInfo.pSetLayouts = layouts.data();
1923 descriptorSets.resize(frameCount, VK_NULL_HANDLE);
1924 if (vkAllocateDescriptorSets(window->getDevice(), &allocInfo, descriptorSets.data()) != VK_SUCCESS) {
1925 throw mxvk::Exception(
"walk: failed to allocate raw wall descriptor sets");
1928 VkDescriptorImageInfo imageInfo{};
1929 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1930 imageInfo.imageView = textureView;
1931 imageInfo.sampler = textureSampler;
1933 for (
size_t i = 0; i < frameCount; ++i) {
1934 VkDescriptorBufferInfo bufferInfo{};
1935 bufferInfo.buffer = uniformBuffers[i];
1936 bufferInfo.offset = 0;
1937 bufferInfo.range =
sizeof(WallUniforms);
1939 std::array<VkWriteDescriptorSet, 2> writes{};
1940 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1941 writes[0].dstSet = descriptorSets[i];
1942 writes[0].dstBinding = 0;
1943 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1944 writes[0].descriptorCount = 1;
1945 writes[0].pImageInfo = &imageInfo;
1947 writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1948 writes[1].dstSet = descriptorSets[i];
1949 writes[1].dstBinding = 1;
1950 writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1951 writes[1].descriptorCount = 1;
1952 writes[1].pBufferInfo = &bufferInfo;
1954 vkUpdateDescriptorSets(window->getDevice(),
static_cast<uint32_t
>(writes.size()), writes.data(), 0,
nullptr);
1958 void createPipeline() {
1959 if (descriptorSetLayout == VK_NULL_HANDLE || vertSpv.empty() || fragSpv.empty() || window->getSwapchainFormat() == VK_FORMAT_UNDEFINED) {
1966 VkPipelineShaderStageCreateInfo vertStage{};
1967 vertStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1968 vertStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
1969 vertStage.module = vertModule;
1970 vertStage.pName =
"main";
1972 VkPipelineShaderStageCreateInfo fragStage{};
1973 fragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1974 fragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
1975 fragStage.module = fragModule;
1976 fragStage.pName =
"main";
1977 const std::array<VkPipelineShaderStageCreateInfo, 2> stages = {vertStage, fragStage};
1979 VkVertexInputBindingDescription binding{};
1980 binding.binding = 0;
1981 binding.stride =
sizeof(WallVertex);
1982 binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1984 std::array<VkVertexInputAttributeDescription, 3> attrs{};
1985 attrs[0] = {0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(WallVertex, position)};
1986 attrs[1] = {1, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(WallVertex, texCoord)};
1987 attrs[2] = {2, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(WallVertex, normal)};
1989 VkPipelineVertexInputStateCreateInfo vertexInput{};
1990 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
1991 vertexInput.vertexBindingDescriptionCount = 1;
1992 vertexInput.pVertexBindingDescriptions = &binding;
1993 vertexInput.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attrs.size());
1994 vertexInput.pVertexAttributeDescriptions = attrs.data();
1996 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
1997 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
1998 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
2000 const std::array<VkDynamicState, 2> dynamicStates = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
2001 VkPipelineDynamicStateCreateInfo dynamicInfo{};
2002 dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
2003 dynamicInfo.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
2004 dynamicInfo.pDynamicStates = dynamicStates.data();
2006 VkPipelineViewportStateCreateInfo viewportState{};
2007 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
2008 viewportState.viewportCount = 1;
2009 viewportState.scissorCount = 1;
2011 VkPipelineRasterizationStateCreateInfo rasterizer{};
2012 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
2013 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
2014 rasterizer.cullMode = VK_CULL_MODE_NONE;
2015 rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
2016 rasterizer.lineWidth = 1.0f;
2018 VkPipelineMultisampleStateCreateInfo multisample{};
2019 multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
2020 multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
2022 VkPipelineDepthStencilStateCreateInfo depthStencil{};
2023 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
2024 depthStencil.depthTestEnable = VK_TRUE;
2025 depthStencil.depthWriteEnable = VK_TRUE;
2026 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
2028 VkPipelineColorBlendAttachmentState blendAttachment{};
2029 blendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
2030 blendAttachment.blendEnable = VK_FALSE;
2032 VkPipelineColorBlendStateCreateInfo colorBlend{};
2033 colorBlend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
2034 colorBlend.attachmentCount = 1;
2035 colorBlend.pAttachments = &blendAttachment;
2037 VkPushConstantRange pushRange{};
2038 pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
2039 pushRange.offset = 0;
2040 pushRange.size =
sizeof(glm::mat4);
2042 VkPipelineLayoutCreateInfo layoutInfo{};
2043 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
2044 layoutInfo.setLayoutCount = 1;
2045 layoutInfo.pSetLayouts = &descriptorSetLayout;
2046 layoutInfo.pushConstantRangeCount = 1;
2047 layoutInfo.pPushConstantRanges = &pushRange;
2049 if (vkCreatePipelineLayout(window->getDevice(), &layoutInfo,
nullptr, &pipelineLayout) != VK_SUCCESS) {
2050 vkDestroyShaderModule(window->getDevice(), fragModule,
nullptr);
2051 vkDestroyShaderModule(window->getDevice(), vertModule,
nullptr);
2052 throw mxvk::Exception(
"walk: failed to create raw wall pipeline layout");
2055 const VkFormat colorFormat = window->getSwapchainFormat();
2056 const VkFormat depthFormat = window->getDepthFormat();
2057 VkPipelineRenderingCreateInfo renderingInfo{};
2058 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
2059 renderingInfo.colorAttachmentCount = 1;
2060 renderingInfo.pColorAttachmentFormats = &colorFormat;
2061 if (depthFormat != VK_FORMAT_UNDEFINED) {
2062 renderingInfo.depthAttachmentFormat = depthFormat;
2065 VkGraphicsPipelineCreateInfo pipelineInfo{};
2066 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
2067 pipelineInfo.pNext = &renderingInfo;
2068 pipelineInfo.stageCount =
static_cast<uint32_t
>(stages.size());
2069 pipelineInfo.pStages = stages.data();
2070 pipelineInfo.pVertexInputState = &vertexInput;
2071 pipelineInfo.pInputAssemblyState = &inputAssembly;
2072 pipelineInfo.pViewportState = &viewportState;
2073 pipelineInfo.pRasterizationState = &rasterizer;
2074 pipelineInfo.pMultisampleState = &multisample;
2075 pipelineInfo.pDepthStencilState = &depthStencil;
2076 pipelineInfo.pColorBlendState = &colorBlend;
2077 pipelineInfo.pDynamicState = &dynamicInfo;
2078 pipelineInfo.layout = pipelineLayout;
2079 pipelineInfo.renderPass = VK_NULL_HANDLE;
2081 if (vkCreateGraphicsPipelines(window->getDevice(), VK_NULL_HANDLE, 1, &pipelineInfo,
nullptr, &pipeline) != VK_SUCCESS) {
2082 vkDestroyPipelineLayout(window->getDevice(), pipelineLayout,
nullptr);
2083 pipelineLayout = VK_NULL_HANDLE;
2084 vkDestroyShaderModule(window->getDevice(), fragModule,
nullptr);
2085 vkDestroyShaderModule(window->getDevice(), vertModule,
nullptr);
2086 throw mxvk::Exception(
"walk: failed to create raw wall graphics pipeline");
2089 vkDestroyShaderModule(window->getDevice(), fragModule,
nullptr);
2090 vkDestroyShaderModule(window->getDevice(), vertModule,
nullptr);
2093 void destroyPipeline() {
2094 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
2095 pipeline = VK_NULL_HANDLE;
2096 pipelineLayout = VK_NULL_HANDLE;
2100 if (pipeline != VK_NULL_HANDLE) {
2101 vkDestroyPipeline(window->getDevice(), pipeline,
nullptr);
2102 pipeline = VK_NULL_HANDLE;
2104 if (pipelineLayout != VK_NULL_HANDLE) {
2105 vkDestroyPipelineLayout(window->getDevice(), pipelineLayout,
nullptr);
2106 pipelineLayout = VK_NULL_HANDLE;
2110 void destroyDescriptors() {
2111 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
2112 descriptorSets.clear();
2113 descriptorPool = VK_NULL_HANDLE;
2114 descriptorSetLayout = VK_NULL_HANDLE;
2115 destroyUniformBuffers();
2119 descriptorSets.clear();
2120 if (descriptorPool != VK_NULL_HANDLE) {
2121 vkDestroyDescriptorPool(window->getDevice(), descriptorPool,
nullptr);
2122 descriptorPool = VK_NULL_HANDLE;
2124 if (descriptorSetLayout != VK_NULL_HANDLE) {
2125 vkDestroyDescriptorSetLayout(window->getDevice(), descriptorSetLayout,
nullptr);
2126 descriptorSetLayout = VK_NULL_HANDLE;
2128 destroyUniformBuffers();
2131 void destroyUniformBuffers() {
2132 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
2133 uniformBuffers.clear();
2134 uniformBufferMemory.clear();
2135 uniformBuffersMapped.clear();
2139 for (
size_t i = 0; i < uniformBuffers.size(); ++i) {
2140 if (uniformBuffersMapped[i] !=
nullptr) {
2141 vkUnmapMemory(window->getDevice(), uniformBufferMemory[i]);
2142 uniformBuffersMapped[i] =
nullptr;
2144 if (uniformBuffers[i] != VK_NULL_HANDLE) {
2145 vkDestroyBuffer(window->getDevice(), uniformBuffers[i],
nullptr);
2147 if (uniformBufferMemory[i] != VK_NULL_HANDLE) {
2148 vkFreeMemory(window->getDevice(), uniformBufferMemory[i],
nullptr);
2152 uniformBuffers.clear();
2153 uniformBufferMemory.clear();
2154 uniformBuffersMapped.clear();
2157 void destroyTexture() {
2158 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
2159 textureView = VK_NULL_HANDLE;
2160 textureImage = VK_NULL_HANDLE;
2161 textureMemory = VK_NULL_HANDLE;
2162 textureSampler = VK_NULL_HANDLE;
2166 if (textureView != VK_NULL_HANDLE) {
2167 vkDestroyImageView(window->getDevice(), textureView,
nullptr);
2168 textureView = VK_NULL_HANDLE;
2170 if (textureImage != VK_NULL_HANDLE) {
2171 vkDestroyImage(window->getDevice(), textureImage,
nullptr);
2172 textureImage = VK_NULL_HANDLE;
2174 if (textureMemory != VK_NULL_HANDLE) {
2175 vkFreeMemory(window->getDevice(), textureMemory,
nullptr);
2176 textureMemory = VK_NULL_HANDLE;
2178 if (textureSampler != VK_NULL_HANDLE) {
2179 vkDestroySampler(window->getDevice(), textureSampler,
nullptr);
2180 textureSampler = VK_NULL_HANDLE;
2184 void destroyBuffers() {
2185 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
2186 vertexBuffer = VK_NULL_HANDLE;
2187 vertexMemory = VK_NULL_HANDLE;
2188 indexBuffer = VK_NULL_HANDLE;
2189 indexMemory = VK_NULL_HANDLE;
2193 if (vertexBuffer != VK_NULL_HANDLE) {
2194 vkDestroyBuffer(window->getDevice(), vertexBuffer,
nullptr);
2195 vertexBuffer = VK_NULL_HANDLE;
2197 if (vertexMemory != VK_NULL_HANDLE) {
2198 vkFreeMemory(window->getDevice(), vertexMemory,
nullptr);
2199 vertexMemory = VK_NULL_HANDLE;
2201 if (indexBuffer != VK_NULL_HANDLE) {
2202 vkDestroyBuffer(window->getDevice(), indexBuffer,
nullptr);
2203 indexBuffer = VK_NULL_HANDLE;
2205 if (indexMemory != VK_NULL_HANDLE) {
2206 vkFreeMemory(window->getDevice(), indexMemory,
nullptr);
2207 indexMemory = VK_NULL_HANDLE;
2211 void createImage(uint32_t width,
2214 VkImageTiling tiling,
2215 VkImageUsageFlags usage,
2216 VkMemoryPropertyFlags properties,
2218 VkDeviceMemory &memory)
const {
2219 VkImageCreateInfo imageInfo{};
2220 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
2221 imageInfo.imageType = VK_IMAGE_TYPE_2D;
2222 imageInfo.extent.width = width;
2223 imageInfo.extent.height = height;
2224 imageInfo.extent.depth = 1;
2225 imageInfo.mipLevels = 1;
2226 imageInfo.arrayLayers = 1;
2227 imageInfo.format = format;
2228 imageInfo.tiling = tiling;
2229 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
2230 imageInfo.usage = usage;
2231 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
2232 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
2234 if (vkCreateImage(window->getDevice(), &imageInfo,
nullptr, &image) != VK_SUCCESS) {
2235 throw mxvk::Exception(
"walk: failed to create raw wall image");
2238 VkMemoryRequirements requirements{};
2239 vkGetImageMemoryRequirements(window->getDevice(), image, &requirements);
2241 VkMemoryAllocateInfo allocInfo{};
2242 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
2243 allocInfo.allocationSize = requirements.size;
2246 allocInfo.memoryTypeIndex = findMemoryType(requirements.memoryTypeBits, properties);
2247 if (vkAllocateMemory(window->getDevice(), &allocInfo,
nullptr, &memory) != VK_SUCCESS) {
2248 throw mxvk::Exception(
"walk: failed to allocate raw wall image memory");
2251 if (vkBindImageMemory(window->getDevice(), image, memory, 0) != VK_SUCCESS) {
2252 throw mxvk::Exception(
"walk: failed to bind raw wall image memory");
2255 if (memory != VK_NULL_HANDLE) {
2256 vkFreeMemory(window->getDevice(), memory,
nullptr);
2257 memory = VK_NULL_HANDLE;
2259 if (image != VK_NULL_HANDLE) {
2260 vkDestroyImage(window->getDevice(), image,
nullptr);
2261 image = VK_NULL_HANDLE;
2267 VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags)
const {
2268 VkImageViewCreateInfo viewInfo{};
2269 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
2270 viewInfo.image = image;
2271 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
2272 viewInfo.format = format;
2273 viewInfo.subresourceRange.aspectMask = aspectFlags;
2274 viewInfo.subresourceRange.baseMipLevel = 0;
2275 viewInfo.subresourceRange.levelCount = 1;
2276 viewInfo.subresourceRange.baseArrayLayer = 0;
2277 viewInfo.subresourceRange.layerCount = 1;
2279 VkImageView imageView = VK_NULL_HANDLE;
2280 if (vkCreateImageView(window->getDevice(), &viewInfo,
nullptr, &imageView) != VK_SUCCESS) {
2281 throw mxvk::Exception(
"walk: failed to create raw wall image view");
2286 [[nodiscard]] VkCommandBuffer beginSingleTimeCommands()
const {
2287 VkCommandBufferAllocateInfo allocInfo{};
2288 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
2289 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
2290 allocInfo.commandPool = window->getCommandPool();
2291 allocInfo.commandBufferCount = 1;
2293 VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
2294 if (vkAllocateCommandBuffers(window->getDevice(), &allocInfo, &commandBuffer) != VK_SUCCESS) {
2295 throw mxvk::Exception(
"walk: failed to allocate raw wall command buffer");
2298 VkCommandBufferBeginInfo beginInfo{};
2299 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
2300 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
2301 if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
2302 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
2303 throw mxvk::Exception(
"walk: failed to begin raw wall command buffer");
2306 return commandBuffer;
2309 void endSingleTimeCommands(VkCommandBuffer commandBuffer)
const {
2310 if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
2311 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
2312 throw mxvk::Exception(
"walk: failed to end raw wall command buffer");
2315 VkSubmitInfo submitInfo{};
2316 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
2317 submitInfo.commandBufferCount = 1;
2318 submitInfo.pCommandBuffers = &commandBuffer;
2320 if (vkQueueSubmit(window->getGraphicsQueue(), 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) {
2321 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
2322 throw mxvk::Exception(
"walk: failed to submit raw wall command buffer");
2324 if (vkQueueWaitIdle(window->getGraphicsQueue()) != VK_SUCCESS) {
2325 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
2326 throw mxvk::Exception(
"walk: failed to wait for raw wall upload queue");
2329 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
2332 void transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout)
const {
2333 VkCommandBuffer cmd = beginSingleTimeCommands();
2335 VkImageMemoryBarrier barrier{};
2336 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
2337 barrier.oldLayout = oldLayout;
2338 barrier.newLayout = newLayout;
2339 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2340 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2341 barrier.image = image;
2342 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2343 barrier.subresourceRange.baseMipLevel = 0;
2344 barrier.subresourceRange.levelCount = 1;
2345 barrier.subresourceRange.baseArrayLayer = 0;
2346 barrier.subresourceRange.layerCount = 1;
2348 VkPipelineStageFlags sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
2349 VkPipelineStageFlags destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2350 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
2351 barrier.srcAccessMask = 0;
2352 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2353 }
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
2354 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2355 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
2356 sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2357 destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
2360 vkCmdPipelineBarrier(cmd, sourceStage, destinationStage, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
2361 endSingleTimeCommands(cmd);
2364 void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height)
const {
2365 VkCommandBuffer cmd = beginSingleTimeCommands();
2366 VkBufferImageCopy region{};
2367 region.bufferOffset = 0;
2368 region.bufferRowLength = 0;
2369 region.bufferImageHeight = 0;
2370 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2371 region.imageSubresource.mipLevel = 0;
2372 region.imageSubresource.baseArrayLayer = 0;
2373 region.imageSubresource.layerCount = 1;
2374 region.imageOffset = {0, 0, 0};
2375 region.imageExtent = {width, height, 1};
2377 vkCmdCopyBufferToImage(cmd, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
2378 endSingleTimeCommands(cmd);
2381 std::vector<char> vertSpv{};
2382 std::vector<char> fragSpv{};
2384 uint32_t vertexCount = 0;
2385 uint32_t indexCount = 0;
2386 VkBuffer vertexBuffer = VK_NULL_HANDLE;
2387 VkDeviceMemory vertexMemory = VK_NULL_HANDLE;
2388 VkBuffer indexBuffer = VK_NULL_HANDLE;
2389 VkDeviceMemory indexMemory = VK_NULL_HANDLE;
2391 VkImage textureImage = VK_NULL_HANDLE;
2392 VkDeviceMemory textureMemory = VK_NULL_HANDLE;
2393 VkImageView textureView = VK_NULL_HANDLE;
2394 VkSampler textureSampler = VK_NULL_HANDLE;
2396 VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE;
2397 VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
2398 std::vector<VkDescriptorSet> descriptorSets{};
2400 VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
2401 VkPipeline pipeline = VK_NULL_HANDLE;
2403 std::vector<VkBuffer> uniformBuffers{};
2404 std::vector<VkDeviceMemory> uniformBufferMemory{};
2405 std::vector<void *> uniformBuffersMapped{};
2407 VkDevice device [[maybe_unused]] = VK_NULL_HANDLE;
2408 mxvk::VK_Window *window =
nullptr;
2414 :
mxvk::
VK_IOWindow(args.path,
"FPS Maze Room - MXVK", args.width, args.height, args.fullscreen, args.enable_vsync),
2415 assetRoot((args.path.empty() || args.path ==
".") ? std::string(WALK_ASSET_DIR) : args.path),
2416 shaderRoot(assetRoot +
"/data"),
2417 modelRoot(assetRoot +
"/data") {
2418 logEnv(std::format(
"initializing window {}x{} (fullscreen={})", args.
width, args.
height, args.
fullscreen ?
"true" :
"false"));
2419 logEnv(std::format(
"asset root: {}", assetRoot));
2420 logEnv(std::format(
"model root: {}", modelRoot));
2422 std::mt19937 rng(
static_cast<uint32_t
>(std::chrono::high_resolution_clock::now().time_since_epoch().count()));
2423 world.generate(rng());
2424 logEnv(std::format(
"world generated (walls={}, pillars={}, collectibles={})",
2425 world.walls().size(),
2426 world.pillars().size(),
2427 world.collectibles().size()));
2428 setClearColor(100.0f / 255.0f, 181.0f / 255.0f, 246.0f / 255.0f, 1.0f);
2430 cameraPos = world.startPosition();
2431 yaw = chooseBestSpawnYaw(cameraPos);
2433 updateCameraVectors();
2435 setFont(assetRoot +
"/data/font.ttf", 22);
2437 const std::string vertPath = shaderRoot +
"/model.vert.spv";
2438 const std::string wallFragPath = shaderRoot +
"/wall.frag.spv";
2439 const std::string floorFragPath = shaderRoot +
"/floor.frag.spv";
2440 const std::string pillarVertPath = shaderRoot +
"/pillar.vert.spv";
2441 const std::string pillarFragPath = shaderRoot +
"/pillar.frag.spv";
2442 const std::string objectFragPath = shaderRoot +
"/object.frag.spv";
2443 const std::string bulletFragPath = shaderRoot +
"/bullet.frag.spv";
2444 const std::string particleFragPath = shaderRoot +
"/particle.frag.spv";
2445 const std::string groundTexManifest = assetRoot +
"/data/ground.tex";
2447 modelVertSpv = vertPath;
2448 pillarVertSpv = pillarVertPath;
2449 wallFragSpv = wallFragPath;
2450 floorFragSpv = floorFragPath;
2451 pillarFragSpv = pillarFragPath;
2452 objectFragSpv = objectFragPath;
2453 bulletFragSpv = bulletFragPath;
2455 loadModel(floorModel, modelRoot +
"/cube.mxmod.z", groundTexManifest, assetRoot +
"/data", vertPath, floorFragPath);
2456 loadModel(bulletModel, modelRoot +
"/sphere.mxmod.z",
"",
"", vertPath, bulletFragPath);
2458 logEnv(
"loading wall renderer assets");
2459 rawWallRenderer.load(
this,
2461 assetRoot +
"/data",
2464 logEnv(
"wall renderer ready");
2466 logEnv(
"loading pillar renderer assets");
2467 rawPillarRenderer.load(
this,
2469 assetRoot +
"/data",
2472 logEnv(
"pillar renderer ready");
2474 loadModel(saturnModel, assetRoot +
"/data/saturn.mxmod.z",
2475 assetRoot +
"/data/planet.tex", assetRoot +
"/data", vertPath, objectFragPath);
2476 loadModel(birdModel, assetRoot +
"/data/tux.obj",
2477 assetRoot +
"/data/tux.mtl", assetRoot +
"/data", vertPath, objectFragPath);
2478 loadModel(blasterModel, assetRoot +
"/data/blaster.obj",
2479 assetRoot +
"/data/blaster.mtl", assetRoot +
"/data", vertPath, objectFragPath);
2480 normalizeCollectiblesToModel();
2482 pointParticleVertSpv = shaderRoot +
"/particle_points.vert.spv";
2483 pointParticleFragSpv = shaderRoot +
"/particle_points.frag.spv";
2484 initializePointParticles();
2485 logEnv(
"point-particle pipeline initialized");
2487 tryOpenFirstGamepad();
2489 logEnv(
"mouse capture enabled");
2493 logEnv(
"shutting down walk window");
2494 if (gamepad !=
nullptr) {
2495 SDL_CloseGamepad(gamepad);
2499 if (
device != VK_NULL_HANDLE) {
2500 vkDeviceWaitIdle(
device);
2501 destroyPointParticles();
2507 const bool is_left_double_click =
2508 (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN &&
2509 e.button.button == SDL_BUTTON_LEFT &&
2510 e.button.clicks >= 2);
2512 if (is_left_double_click) {
2513 if (SDL_Window *
const sdlWindow =
getSDLWindow(); sdlWindow !=
nullptr) {
2514 SDL_RaiseWindow(sdlWindow);
2515 SDL_SetWindowMouseGrab(sdlWindow,
true);
2516 SDL_SetWindowRelativeMouseMode(sdlWindow,
true);
2519 mouseCapture =
true;
2522 suppressProjectileOnNextLeftDown =
true;
2524 logEnv(
"mouse capture enabled (double-click)");
2531 if (e.type == SDL_EVENT_QUIT) {
2532 logEnv(
"received quit event");
2537 if (e.type == SDL_EVENT_KEY_DOWN) {
2538 if (e.key.key == SDLK_ESCAPE) {
2540 mouseCapture =
false;
2542 suppressProjectileOnNextLeftDown =
false;
2543 logEnv(
"mouse capture disabled (ESC)");
2545 logEnv(
"exit requested by ESC");
2549 }
else if (e.key.key == SDLK_F) {
2551 logEnv(std::format(
"FPS overlay {}", showFps ?
"enabled" :
"disabled"));
2555 if (e.type == SDL_EVENT_GAMEPAD_ADDED) {
2556 logEnv(std::format(
"gamepad added (id={})",
static_cast<int>(e.gdevice.which)));
2557 openGamepad(e.gdevice.which);
2560 if (e.type == SDL_EVENT_GAMEPAD_REMOVED) {
2561 if (gamepad !=
nullptr && e.gdevice.which == gamepadId) {
2562 logEnv(std::format(
"gamepad removed (id={})",
static_cast<int>(e.gdevice.which)));
2563 SDL_CloseGamepad(gamepad);
2569 if (e.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
2570 if (e.gbutton.button == SDL_GAMEPAD_BUTTON_BACK || e.gbutton.button == SDL_GAMEPAD_BUTTON_START) {
2571 logEnv(
"exit requested by gamepad back/start");
2573 }
else if (e.gbutton.button == SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER) {
2575 }
else if (e.gbutton.button == SDL_GAMEPAD_BUTTON_SOUTH && cameraPos.y <= 1.71f) {
2576 jumpVelocity = 0.3f;
2577 logEnv(
"jump triggered by gamepad");
2581 if (e.type == SDL_EVENT_MOUSE_MOTION && mouseCapture) {
2586 yaw +=
static_cast<float>(e.motion.xrel) * mouseSensitivity;
2587 pitch -=
static_cast<float>(e.motion.yrel) * mouseSensitivity;
2588 pitch = glm::clamp(pitch, -89.0f, 89.0f);
2589 updateCameraVectors();
2592 if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN && e.button.button == SDL_BUTTON_LEFT && mouseCapture) {
2593 if (suppressProjectileOnNextLeftDown) {
2594 suppressProjectileOnNextLeftDown =
false;
2602 tryOpenFirstGamepad();
2603 const auto now = std::chrono::steady_clock::now();
2604 float deltaTime = std::chrono::duration<float>(now - lastTick).count();
2606 deltaTime = std::clamp(deltaTime, 0.0f, 0.05f);
2609 updatePlayer(deltaTime);
2611 updateProjectiles(deltaTime);
2612 updateExplosions(deltaTime);
2613 updateCollectibles(deltaTime);
2615 const int aliveObjects = world.activeCollectibles();
2617 printText(std::format(
"Objects left: {}", aliveObjects), 20, 20, {255, 255, 255, 255});
2618 printText(std::format(
"Active Bullets: {}", bullets.size()), 20, 48, {255, 220, 120, 255});
2619 if (showFps && deltaTime > 0.0001f) {
2620 const int fps =
static_cast<int>(1.0f / deltaTime);
2621 printText(std::format(
"FPS: {}", fps), 20, 76, {120, 255, 120, 255});
2625 const int cx =
static_cast<int>(extent.width / 2U);
2626 const int cy =
static_cast<int>(extent.height / 2U);
2627 printText(
"+", cx - 6, cy - 12, {255, 64, 64, 255});
2628 printText(
"3D Room - WASD/Left Stick move, Mouse/Right Stick look, Click/RB shoot, Back/Start quit", 20,
static_cast<int>(extent.height) - 36, {210, 210, 210, 255});
2633 logEnv(
"swapchain recreated; resizing render resources");
2634 floorModel.resize(
this);
2635 rawWallRenderer.resize(
this);
2636 rawPillarRenderer.resize(
this);
2637 saturnModel.resize(
this);
2638 birdModel.resize(
this);
2639 blasterModel.resize(
this);
2640 bulletModel.resize(
this);
2641 rebuildPointParticlePipeline();
2646 const float aspect = (extent.height > 0U)
2647 ?
static_cast<float>(extent.width) /
static_cast<float>(extent.height)
2650 const glm::mat4 view = glm::lookAt(cameraPos, cameraPos + cameraFront, glm::vec3(0.0f, 1.0f, 0.0f));
2651 glm::mat4 proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 1000.0f);
2652 proj[1][1] *= -1.0f;
2654 const float t =
static_cast<float>(SDL_GetTicks()) * 0.001f;
2658 constexpr float floorHalfSize = 100.0f;
2659 constexpr float floorThickness = 0.04f;
2660 const glm::vec3 extent = floorModel.modelAxisExtent();
2661 const glm::vec3 srcScale(
2662 (floorHalfSize * 2.0f) / std::max(extent.x, 1e-4f),
2663 floorThickness / std::max(extent.y, 1e-4f),
2664 (floorHalfSize * 2.0f) / std::max(extent.z, 1e-4f));
2665 glm::mat4 floorWorld = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, -0.02f, 0.0f));
2666 floorWorld = glm::scale(floorWorld, srcScale);
2667 renderModel(cmd, imageIndex, floorModel, floorWorld, view, proj,
2668 glm::vec4(0.0f, 0.0f, 0.0f, t),
false);
2671 rawWallRenderer.render(cmd,
2674 world.wallThickness(),
2677 glm::vec4(0.58f, 0.58f, 0.65f, t));
2679 rawPillarRenderer.render(cmd, imageIndex, world.pillars(), view, proj, glm::vec4(0.0f, 0.0f, 0.0f, t));
2681 for (
const Collectible &obj : world.collectibles()) {
2685 glm::mat4 world = glm::translate(glm::mat4(1.0f), obj.position);
2686 world = glm::rotate(world, glm::radians(obj.rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
2687 world = glm::scale(world, obj.scale);
2689 renderRawModel(cmd, imageIndex, saturnModel, world, view, proj, glm::vec4(cameraPos, 0.0f));
2691 renderRawModel(cmd, imageIndex, birdModel, world, view, proj, glm::vec4(cameraPos, 0.0f));
2699 blasterWorldTransform(),
2702 glm::vec4(cameraPos, 0.0f));
2706 if (!bullet.active) {
2709 if (bullet.lifetime < 0.05f) {
2712 glm::mat4 world = glm::translate(glm::mat4(1.0f), bullet.position);
2713 world = glm::scale(world, glm::vec3(0.07f, 0.07f, 0.20f));
2714 const float fadeProgress = glm::clamp(bullet.lifetime / bullet.maxLifetime, 0.0f, 1.0f);
2715 const float distanceProgress = glm::clamp(bullet.distanceTraveled / bullet.maxDistance, 0.0f, 1.0f);
2716 const float alpha = std::min(1.0f - fadeProgress, 1.0f - distanceProgress);
2717 renderRawModel(cmd, imageIndex, bulletModel, world, view, proj, glm::vec4(alpha, 0.0f, 0.0f, 0.0f));
2719 renderPointParticles(cmd, view, proj);
2723 enum class ProjectileHitType {
2730 struct ProjectileTraceHit {
2731 ProjectileHitType type = ProjectileHitType::None;
2732 glm::vec3 impact{0.0f};
2733 size_t collectibleIndex = 0;
2736 [[nodiscard]] ProjectileTraceHit traceProjectileSegment(
const glm::vec3 &from,
const glm::vec3 &to)
const {
2737 const glm::vec3 dir = to - from;
2738 const float travel = glm::length(dir);
2739 if (travel <= 1e-8f) {
2743 constexpr float sampleStride = 0.03f;
2744 constexpr float projectileRadius = 0.015f;
2745 const int steps = std::max(1,
static_cast<int>(std::ceil(travel / sampleStride)));
2746 for (
int i = 0; i <= steps; ++i) {
2747 const float t =
static_cast<float>(i) /
static_cast<float>(steps);
2748 const glm::vec3 point = from + (dir * t);
2749 if (pointHitsWall3D(point, projectileRadius)) {
2750 return {ProjectileHitType::Wall, point, 0};
2752 if (pointHitsPillar3D(point, projectileRadius)) {
2753 return {ProjectileHitType::Pillar, point, 0};
2755 if (point.y <= 0.0f) {
2756 return {ProjectileHitType::Floor, point, 0};
2763 bool handleConsoleCommand(
const std::vector<std::string> &args, std::ostream &out)
override {
2768 const std::string &cmd = args[0];
2770 if (cmd ==
"spawn_random" || (cmd ==
"spawn" && args.size() >= 2 && args[1] ==
"random")) {
2771 const int attempts = (args.size() >= 3 && cmd ==
"spawn") ? parseIntOrDefault(args[2], 128)
2772 : ((args.size() >= 2 && cmd ==
"spawn_random") ? parseIntOrDefault(args[1], 128) : 128);
2773 glm::vec3 candidate = cameraPos;
2774 if (!sampleNavigablePoint(1.7f, 0.68f, candidate, std::max(1, attempts))) {
2775 candidate = world.startPosition();
2778 cameraPos = candidate;
2779 yaw = chooseBestSpawnYaw(cameraPos);
2781 updateCameraVectors();
2783 out << std::format(
"Spawned at random location ({:.2f}, {:.2f}, {:.2f})", cameraPos.x, cameraPos.y, cameraPos.z);
2784 logEnv(
"command: spawn_random");
2788 if (cmd ==
"reset" || cmd ==
"reset_collectibles") {
2789 std::vector<Collectible> &collectibles = world.collectibles();
2790 for (
size_t i = 0; i < collectibles.size(); ++i) {
2791 Collectible &obj = collectibles[i];
2793 obj.rotation = glm::vec3(0.0f);
2794 relocateCollectible(i, 2.0f, 128);
2796 resolveCollectibleClusters(2.0f, 4);
2798 out << std::format(
"Collectibles reset. Active collectibles: {}", world.activeCollectibles());
2799 logEnv(
"command: reset collectibles");
2803 if (cmd ==
"add_collectibles" || cmd ==
"add_collectables") {
2804 const int requested = (args.size() >= 2) ? parseIntOrDefault(args[1], 10) : 10;
2805 const int toAdd = std::clamp(requested, 1, 200);
2806 std::uniform_int_distribution<int> typeDist(0, 1);
2807 std::uniform_real_distribution<float> saturnScale(0.4f, 0.8f);
2808 std::uniform_real_distribution<float> saturnRotSpeed(5.0f, 15.0f);
2809 std::uniform_real_distribution<float> birdScale(0.3f, 0.5f);
2810 std::uniform_real_distribution<float> birdRotSpeed(20.0f, 60.0f);
2813 for (
int i = 0; i < toAdd; ++i) {
2815 obj.type = (typeDist(rng) == 0) ? Collectible::Type::Saturn : Collectible::Type::Bird;
2816 if (obj.type == Collectible::Type::Saturn) {
2817 const float scale = saturnScale(rng);
2818 obj.scale = glm::vec3(scale);
2819 obj.rotationSpeed = saturnRotSpeed(rng);
2820 obj.radius = saturnHitRadiusForScale(scale);
2821 obj.hitCenterOffset = saturnHitCenterOffsetForScale(scale);
2823 const float scale = birdScale(rng);
2824 obj.scale = glm::vec3(scale);
2825 obj.rotationSpeed = birdRotSpeed(rng);
2826 obj.radius = birdHitHalfSideForScale(scale);
2827 obj.hitCenterOffset = birdHitCenterOffsetForScale(scale);
2830 bool placed =
false;
2831 for (
int attempt = 0; attempt < 96; ++attempt) {
2832 const float y = (obj.type == Collectible::Type::Bird) ? birdGroundYForScale(obj.scale.x) : 2.5f;
2833 const float placementRadius = placementRadiusForCollectible(obj);
2834 glm::vec3 candidate{};
2835 if (!sampleNavigablePoint(y, placementRadius, candidate, 1)) {
2839 bool overlaps =
false;
2840 for (
const Collectible &existing : world.collectibles()) {
2841 if (!existing.active) {
2844 const float separation = std::max(5.0f, existing.radius + obj.radius + 0.2f);
2845 if (glm::length(existing.position - candidate) < separation) {
2852 obj.position = candidate;
2859 world.collectibles().push_back(obj);
2864 out << std::format(
"Added {} collectible(s). Active collectibles: {}",
2866 world.activeCollectibles());
2867 resolveCollectibleClusters(2.0f, 4);
2868 logEnv(std::format(
"command: add_collectibles requested={} added={}", toAdd, added));
2872 if (cmd ==
"status") {
2873 out << std::format(
"pos=({:.2f}, {:.2f}, {:.2f}) yaw={:.2f} pitch={:.2f}\n"
2874 "walls={} pillars={} collectibles(active/total)={}/{} bullets={} particles={} destroyed={}",
2880 world.walls().size(),
2881 world.pillars().size(),
2882 world.activeCollectibles(),
2883 world.collectibles().size(),
2885 explosionParticles.size(),
2890 if (cmd ==
"teleport") {
2891 if (args.size() < 4) {
2892 out <<
"Usage: teleport <x> <y> <z>";
2899 if (!tryParseFloat(args[1], x) || !tryParseFloat(args[2], y) || !tryParseFloat(args[3], z)) {
2900 out <<
"teleport: invalid numeric argument(s)";
2904 const glm::vec3 candidate{x, y, z};
2905 if (world.checkWallCollision(candidate, 0.68f) || world.checkPillarCollision(candidate, 0.68f)) {
2906 out <<
"teleport blocked: target intersects wall/pillar";
2910 cameraPos = candidate;
2911 out << std::format(
"Teleported to ({:.2f}, {:.2f}, {:.2f})", x, y, z);
2912 logEnv(
"command: teleport");
2916 if (cmd ==
"clear_bullets") {
2917 const std::size_t removed = bullets.size();
2919 out << std::format(
"Cleared {} bullet(s)", removed);
2923 if (cmd ==
"clear_fx") {
2924 const std::size_t removed = explosionParticles.size();
2925 explosionParticles.clear();
2926 out << std::format(
"Cleared {} particle effect(s)", removed);
2930 if (cmd ==
"set_fps") {
2931 if (args.size() < 2) {
2932 out << std::format(
"FPS overlay is currently {}. Usage: set_fps <on|off>", showFps ?
"on" :
"off");
2935 const std::string value = toLowerCopy(args[1]);
2936 if (value ==
"on" || value ==
"1" || value ==
"true") {
2938 out <<
"FPS overlay enabled";
2941 if (value ==
"off" || value ==
"0" || value ==
"false") {
2943 out <<
"FPS overlay disabled";
2947 out <<
"Usage: set_fps <on|off>";
2951 if (cmd ==
"regen_world") {
2952 const uint32_t seed = (args.size() >= 2) ?
static_cast<uint32_t
>(parseIntOrDefault(args[1],
static_cast<int>(
rng())))
2954 world.generate(seed);
2955 normalizeCollectiblesToModel();
2956 cameraPos = world.startPosition();
2957 yaw = chooseBestSpawnYaw(cameraPos);
2959 updateCameraVectors();
2961 explosionParticles.clear();
2964 out << std::format(
"Regenerated world with seed {} (walls={}, pillars={}, collectibles={})",
2966 world.walls().size(),
2967 world.pillars().size(),
2968 world.collectibles().size());
2969 logEnv(std::format(
"command: regen_world seed={}", seed));
2973 if (cmd ==
"set_wall" || cmd ==
"set_floor" || cmd ==
"set_pillar" || cmd ==
"set_object" || cmd ==
"set_bullet") {
2974 if (args.size() < 2) {
2975 out << std::format(
"Usage: {} <shader.spv|full/path/to/shader.spv>", cmd);
2979 const std::string shaderPath = resolveShaderPath(args[1]);
2980 std::vector<char> shaderBytes;
2982 shaderBytes = loadSpv(shaderPath);
2983 }
catch (
const mxvk::Exception &e) {
2984 out << std::format(
"{}: failed to load shader '{}': {}", cmd, shaderPath, e.
text());
2988 if (shaderBytes.empty()) {
2989 out << std::format(
"{}: shader '{}' is empty", cmd, shaderPath);
2993 if (cmd ==
"set_wall") {
2994 wallFragSpv = shaderPath;
2995 rawWallRenderer.reloadFragShader(shaderBytes);
2996 out << std::format(
"Wall shader reloaded from {}", shaderPath);
2997 }
else if (cmd ==
"set_floor") {
2998 floorFragSpv = shaderPath;
2999 floorModel.setShaders(
this, modelVertSpv, shaderPath);
3000 out << std::format(
"Floor shader reloaded from {}", shaderPath);
3001 }
else if (cmd ==
"set_pillar") {
3002 pillarFragSpv = shaderPath;
3003 rawPillarRenderer.reloadFragShader(shaderBytes);
3004 out << std::format(
"Pillar shader reloaded from {}", shaderPath);
3005 }
else if (cmd ==
"set_object") {
3006 objectFragSpv = shaderPath;
3007 saturnModel.setShaders(
this, modelVertSpv, shaderPath);
3008 birdModel.setShaders(
this, modelVertSpv, shaderPath);
3009 out << std::format(
"Object shader reloaded from {}", shaderPath);
3010 }
else if (cmd ==
"set_bullet") {
3011 bulletFragSpv = shaderPath;
3012 bulletModel.setShaders(
this, modelVertSpv, shaderPath);
3013 out << std::format(
"Bullet shader reloaded from {}", shaderPath);
3016 logEnv(std::format(
"command: {} shader={}", cmd, shaderPath));
3020 if (cmd ==
"list_shaders") {
3021 const std::array<std::pair<std::string_view, std::string>, 11> shaders{{
3022 {
"wall.frag", resolveShaderPath(
"wall.frag.spv")},
3023 {
"floor.frag", resolveShaderPath(
"floor.frag.spv")},
3024 {
"pillar.frag", resolveShaderPath(
"pillar.frag.spv")},
3025 {
"object.frag", resolveShaderPath(
"object.frag.spv")},
3026 {
"bullet.frag", resolveShaderPath(
"bullet.frag.spv")},
3027 {
"particle.frag", resolveShaderPath(
"particle.frag.spv")},
3028 {
"particle_points.frag", resolveShaderPath(
"particle_points.frag.spv")},
3029 {
"bubble.frag", resolveShaderPath(
"bubble.frag.spv")},
3030 {
"floor_kale.frag", resolveShaderPath(
"floor_kale.frag.spv")},
3031 {
"floor_swirl.frag", resolveShaderPath(
"floor_swirl.frag.spv")},
3032 {
"floor_twist.frag", resolveShaderPath(
"floor_twist.frag.spv")},
3035 out <<
"Available shaders:\n";
3036 for (
const auto &[name, path] : shaders) {
3037 out << std::format(
" {:<20} {}\n", name, path);
3039 out << std::format(
"Current bindings:\n"
3056 void appendConsoleHelp(std::ostream &out)
const override {
3057 out <<
"\nWalk debug commands:\n"
3058 <<
" spawn_random [attempts] Spawn player at random valid location\n"
3059 <<
" spawn random [attempts] Alias for spawn_random\n"
3060 <<
" reset Reset all collectibles to active\n"
3061 <<
" add_collectibles [count] Add random collectibles (alias: add_collectables)\n"
3062 <<
" status Print camera/world/debug state\n"
3063 <<
" teleport <x> <y> <z> Teleport player if destination is valid\n"
3064 <<
" clear_bullets Remove all active bullets\n"
3065 <<
" clear_fx Remove all active explosion particles\n"
3066 <<
" set_fps <on|off> Toggle FPS overlay\n"
3067 <<
" set_wall <shader.spv> Reload wall fragment shader\n"
3068 <<
" set_floor <shader.spv> Reload floor fragment shader\n"
3069 <<
" set_pillar <shader.spv> Reload pillar fragment shader\n"
3070 <<
" set_object <shader.spv> Reload object fragment shader\n"
3071 <<
" set_bullet <shader.spv> Reload bullet fragment shader\n"
3072 <<
" list_shaders Print available shaders and current bindings\n"
3073 <<
" regen_world [seed] Regenerate maze, pillars, and collectibles";
3076 void logEnv(
const std::string &message) {
3077 print(std::format(
"[walk] {}", message), {255, 100, 255, 255});
3085 [[nodiscard]] std::string resolveShaderPath(
const std::string &name)
const {
3086 const std::string runtimePath = shaderRoot +
"/" + name;
3087 if (std::filesystem::exists(runtimePath)) {
3093 [[nodiscard]]
static std::string toLowerCopy(std::string value) {
3094 std::transform(value.begin(), value.end(), value.begin(), [](
const unsigned char ch) {
3095 return static_cast<char>(std::tolower(ch));
3100 [[nodiscard]]
static int parseIntOrDefault(
const std::string &text,
const int fallback) {
3101 int value = fallback;
3102 const auto begin = text.data();
3103 const auto end = text.data() + text.size();
3104 const auto [ptr, ec] = std::from_chars(begin, end, value);
3105 if (ec != std::errc{} || ptr != end) {
3111 [[nodiscard]]
static bool tryParseFloat(
const std::string &text,
float &outValue) {
3114 const float value = std::stof(text, &parsed);
3115 if (parsed != text.size()) {
3125 bool sampleNavigablePoint(
const float y,
const float radius, glm::vec3 &outPoint,
const int maxAttempts) {
3126 float minX = -50.0f;
3128 float minZ = -50.0f;
3131 bool haveBounds =
false;
3132 for (
const WallSegment &wall : world.walls()) {
3134 minX = std::min(wall.start.x, wall.end.x);
3135 maxX = std::max(wall.start.x, wall.end.x);
3136 minZ = std::min(wall.start.z, wall.end.z);
3137 maxZ = std::max(wall.start.z, wall.end.z);
3140 minX = std::min(minX, std::min(wall.start.x, wall.end.x));
3141 maxX = std::max(maxX, std::max(wall.start.x, wall.end.x));
3142 minZ = std::min(minZ, std::min(wall.start.z, wall.end.z));
3143 maxZ = std::max(maxZ, std::max(wall.start.z, wall.end.z));
3148 outPoint = world.startPosition();
3153 const float margin = std::max(0.8f, radius + 0.5f);
3159 if (minX > maxX || minZ > maxZ) {
3160 outPoint = world.startPosition();
3165 std::uniform_real_distribution<float> distX(minX, maxX);
3166 std::uniform_real_distribution<float> distZ(minZ, maxZ);
3167 for (
int i = 0; i < maxAttempts; ++i) {
3168 const glm::vec3 candidate{distX(rng), y, distZ(rng)};
3169 if (!world.checkWallCollision(candidate, radius) && !world.checkPillarCollision(candidate, radius)) {
3170 outPoint = candidate;
3178 [[nodiscard]]
static const char *collectibleTypeName(Collectible::Type type)
noexcept {
3179 return type == Collectible::Type::Saturn ?
"saturn" :
"bird";
3182 void loadModel(mxvk::VKAbstractModel &model,
3183 const std::string &modelPath,
3184 const std::string &textureManifest,
3185 const std::string &textureBase,
3186 const std::string &vertSpv,
3187 const std::string &fragSpv,
3188 bool backfaceCulling =
false) {
3189 logEnv(std::format(
"loading model '{}'", modelPath));
3190 model.
load(
this, modelPath, textureManifest, textureBase, 1.0f);
3193 logEnv(std::format(
"model ready '{}'", modelPath));
3196 void cleanupModels() {
3197 floorModel.cleanup(
this);
3198 rawWallRenderer.cleanup(
this);
3199 rawPillarRenderer.cleanup(
this);
3201 saturnModel.cleanup(
this);
3202 birdModel.cleanup(
this);
3203 blasterModel.cleanup(
this);
3204 bulletModel.cleanup(
this);
3207 [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
const {
3208 VkPhysicalDeviceMemoryProperties memProperties{};
3209 vkGetPhysicalDeviceMemoryProperties(getPhysicalDevice(), &memProperties);
3210 for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) {
3211 if ((typeFilter & (1u << i)) != 0u && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
3215 throw mxvk::Exception(
"walk: failed to find suitable Vulkan memory type for point particles");
3218 void initializePointParticles() {
3219 if (!ensureRenderResources()) {
3220 throw mxvk::Exception(
"walk: render resources unavailable for point particles");
3223 destroyPointParticles();
3225 VkBufferCreateInfo bufferInfo{};
3226 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
3227 bufferInfo.size = maxPointVertices *
sizeof(ParticlePointVertex);
3228 bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
3229 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
3230 if (vkCreateBuffer(getDevice(), &bufferInfo,
nullptr, &pointVertexBuffer) != VK_SUCCESS) {
3231 throw mxvk::Exception(
"walk: failed to create point particle vertex buffer");
3234 VkMemoryRequirements memReq{};
3235 vkGetBufferMemoryRequirements(getDevice(), pointVertexBuffer, &memReq);
3236 VkMemoryAllocateInfo allocInfo{};
3237 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
3238 allocInfo.allocationSize = memReq.size;
3239 allocInfo.memoryTypeIndex = findMemoryType(memReq.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
3240 if (vkAllocateMemory(getDevice(), &allocInfo,
nullptr, &pointVertexMemory) != VK_SUCCESS) {
3241 throw mxvk::Exception(
"walk: failed to allocate point particle vertex memory");
3243 if (vkBindBufferMemory(getDevice(), pointVertexBuffer, pointVertexMemory, 0) != VK_SUCCESS) {
3244 throw mxvk::Exception(
"walk: failed to bind point particle vertex memory");
3246 if (vkMapMemory(getDevice(), pointVertexMemory, 0, bufferInfo.size, 0, &pointVertexMapped) != VK_SUCCESS) {
3247 throw mxvk::Exception(
"walk: failed to map point particle vertex memory");
3250 rebuildPointParticlePipeline();
3252 destroyPointParticles();
3257 void rebuildPointParticlePipeline() {
3258 if (pointPipeline != VK_NULL_HANDLE) {
3259 vkDestroyPipeline(getDevice(), pointPipeline,
nullptr);
3260 pointPipeline = VK_NULL_HANDLE;
3262 if (pointPipelineLayout != VK_NULL_HANDLE) {
3263 vkDestroyPipelineLayout(getDevice(), pointPipelineLayout,
nullptr);
3264 pointPipelineLayout = VK_NULL_HANDLE;
3267 if (getSwapchainFormat() == VK_FORMAT_UNDEFINED) {
3271 const std::vector<char> vertBytes = loadSpv(pointParticleVertSpv);
3272 const std::vector<char> fragBytes = loadSpv(pointParticleFragSpv);
3276 VkPipelineShaderStageCreateInfo vertStage{};
3277 vertStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
3278 vertStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
3279 vertStage.module = vertModule;
3280 vertStage.pName =
"main";
3282 VkPipelineShaderStageCreateInfo fragStage{};
3283 fragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
3284 fragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
3285 fragStage.module = fragModule;
3286 fragStage.pName =
"main";
3287 const std::array<VkPipelineShaderStageCreateInfo, 2> stages = {vertStage, fragStage};
3289 VkVertexInputBindingDescription binding{};
3290 binding.binding = 0;
3291 binding.stride =
sizeof(ParticlePointVertex);
3292 binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
3294 std::array<VkVertexInputAttributeDescription, 3> attrs{};
3295 attrs[0] = {0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(ParticlePointVertex, pos)};
3296 attrs[1] = {1, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(ParticlePointVertex, color)};
3297 attrs[2] = {2, 0, VK_FORMAT_R32_SFLOAT, offsetof(ParticlePointVertex, size)};
3299 VkPipelineVertexInputStateCreateInfo vertexInput{};
3300 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
3301 vertexInput.vertexBindingDescriptionCount = 1;
3302 vertexInput.pVertexBindingDescriptions = &binding;
3303 vertexInput.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attrs.size());
3304 vertexInput.pVertexAttributeDescriptions = attrs.data();
3306 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
3307 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
3308 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
3310 const std::array<VkDynamicState, 2> dynamicStates = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
3311 VkPipelineDynamicStateCreateInfo dynamicInfo{};
3312 dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
3313 dynamicInfo.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
3314 dynamicInfo.pDynamicStates = dynamicStates.data();
3316 VkPipelineViewportStateCreateInfo viewportState{};
3317 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
3318 viewportState.viewportCount = 1;
3319 viewportState.scissorCount = 1;
3321 VkPipelineRasterizationStateCreateInfo rasterizer{};
3322 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
3323 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
3324 rasterizer.cullMode = VK_CULL_MODE_NONE;
3325 rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
3326 rasterizer.lineWidth = 1.0f;
3328 VkPipelineMultisampleStateCreateInfo multisample{};
3329 multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
3330 multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
3332 VkPipelineDepthStencilStateCreateInfo depthStencil{};
3333 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
3334 depthStencil.depthTestEnable = VK_FALSE;
3335 depthStencil.depthWriteEnable = VK_FALSE;
3336 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
3338 VkPipelineColorBlendAttachmentState blendAttachment{};
3339 blendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
3340 blendAttachment.blendEnable = VK_TRUE;
3341 blendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
3342 blendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
3343 blendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
3344 blendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
3345 blendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
3346 blendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
3348 VkPipelineColorBlendStateCreateInfo colorBlend{};
3349 colorBlend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
3350 colorBlend.attachmentCount = 1;
3351 colorBlend.pAttachments = &blendAttachment;
3353 VkPushConstantRange pushRange{};
3354 pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
3355 pushRange.offset = 0;
3356 pushRange.size =
sizeof(glm::mat4);
3358 VkPipelineLayoutCreateInfo layoutInfo{};
3359 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
3360 layoutInfo.pushConstantRangeCount = 1;
3361 layoutInfo.pPushConstantRanges = &pushRange;
3362 if (vkCreatePipelineLayout(getDevice(), &layoutInfo,
nullptr, &pointPipelineLayout) != VK_SUCCESS) {
3363 vkDestroyShaderModule(getDevice(), fragModule,
nullptr);
3364 vkDestroyShaderModule(getDevice(), vertModule,
nullptr);
3365 throw mxvk::Exception(
"walk: failed to create point particle pipeline layout");
3368 const VkFormat colorFormat = getSwapchainFormat();
3369 const VkFormat depthFormat = getDepthFormat();
3370 VkPipelineRenderingCreateInfo renderingInfo{};
3371 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
3372 renderingInfo.colorAttachmentCount = 1;
3373 renderingInfo.pColorAttachmentFormats = &colorFormat;
3374 if (depthFormat != VK_FORMAT_UNDEFINED) {
3375 renderingInfo.depthAttachmentFormat = depthFormat;
3378 VkGraphicsPipelineCreateInfo pipelineInfo{};
3379 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
3380 pipelineInfo.pNext = &renderingInfo;
3381 pipelineInfo.stageCount =
static_cast<uint32_t
>(stages.size());
3382 pipelineInfo.pStages = stages.data();
3383 pipelineInfo.pVertexInputState = &vertexInput;
3384 pipelineInfo.pInputAssemblyState = &inputAssembly;
3385 pipelineInfo.pViewportState = &viewportState;
3386 pipelineInfo.pRasterizationState = &rasterizer;
3387 pipelineInfo.pMultisampleState = &multisample;
3388 pipelineInfo.pDepthStencilState = &depthStencil;
3389 pipelineInfo.pColorBlendState = &colorBlend;
3390 pipelineInfo.pDynamicState = &dynamicInfo;
3391 pipelineInfo.layout = pointPipelineLayout;
3392 pipelineInfo.renderPass = VK_NULL_HANDLE;
3394 if (vkCreateGraphicsPipelines(getDevice(), VK_NULL_HANDLE, 1, &pipelineInfo,
nullptr, &pointPipeline) != VK_SUCCESS) {
3395 vkDestroyShaderModule(getDevice(), fragModule,
nullptr);
3396 vkDestroyShaderModule(getDevice(), vertModule,
nullptr);
3397 throw mxvk::Exception(
"walk: failed to create point particle graphics pipeline");
3400 vkDestroyShaderModule(getDevice(), fragModule,
nullptr);
3401 vkDestroyShaderModule(getDevice(), vertModule,
nullptr);
3404 void destroyPointParticles() {
3405 if (pointPipeline != VK_NULL_HANDLE) {
3406 vkDestroyPipeline(getDevice(), pointPipeline,
nullptr);
3407 pointPipeline = VK_NULL_HANDLE;
3409 if (pointPipelineLayout != VK_NULL_HANDLE) {
3410 vkDestroyPipelineLayout(getDevice(), pointPipelineLayout,
nullptr);
3411 pointPipelineLayout = VK_NULL_HANDLE;
3413 if (pointVertexMapped !=
nullptr) {
3414 vkUnmapMemory(getDevice(), pointVertexMemory);
3415 pointVertexMapped =
nullptr;
3417 if (pointVertexBuffer != VK_NULL_HANDLE) {
3418 vkDestroyBuffer(getDevice(), pointVertexBuffer,
nullptr);
3419 pointVertexBuffer = VK_NULL_HANDLE;
3421 if (pointVertexMemory != VK_NULL_HANDLE) {
3422 vkFreeMemory(getDevice(), pointVertexMemory,
nullptr);
3423 pointVertexMemory = VK_NULL_HANDLE;
3427 void renderPointParticles(VkCommandBuffer cmd,
const glm::mat4 &view,
const glm::mat4 &proj) {
3428 if (pointPipeline == VK_NULL_HANDLE || pointPipelineLayout == VK_NULL_HANDLE || pointVertexMapped ==
nullptr) {
3432 std::vector<ParticlePointVertex> vertices{};
3433 vertices.reserve(2048);
3435 for (
const Projectile &bullet : bullets) {
3436 if (!bullet.active) {
3439 for (
const Projectile::TrailPoint &point : bullet.trail) {
3440 const float life = glm::clamp(point.lifetime / point.maxLifetime, 0.0f, 1.0f);
3441 const float fade = 1.0f - life;
3442 vertices.push_back({point.position, glm::vec4(1.0f, 0.2f, 0.0f, fade * 0.8f), 12.0f});
3446 for (
const ExplosionParticle &particle : explosionParticles) {
3447 if (!particle.active) {
3450 const float life = glm::clamp(particle.lifetime / particle.maxLifetime, 0.0f, 1.0f);
3451 const float fade = 1.0f - life;
3452 const float sizePx = glm::clamp(particle.size * 320.0f, 12.0f, 160.0f);
3453 vertices.push_back({particle.position, glm::vec4(particle.color, fade), sizePx});
3456 if (vertices.empty()) {
3460 if (vertices.size() > maxPointVertices) {
3461 vertices.resize(maxPointVertices);
3463 std::memcpy(pointVertexMapped, vertices.data(), vertices.size() *
sizeof(ParticlePointVertex));
3465 const VkBuffer vb = pointVertexBuffer;
3466 const VkDeviceSize offset = 0;
3467 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pointPipeline);
3468 vkCmdBindVertexBuffers(cmd, 0, 1, &vb, &offset);
3469 const glm::mat4 vp = proj * view;
3470 vkCmdPushConstants(cmd, pointPipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0,
sizeof(glm::mat4), &vp);
3471 vkCmdDraw(cmd,
static_cast<uint32_t
>(vertices.size()), 1, 0, 0);
3474 bool openGamepad(SDL_JoystickID
id) {
3475 if (gamepad !=
nullptr && gamepadId ==
id) {
3478 if (gamepad !=
nullptr) {
3479 SDL_CloseGamepad(gamepad);
3483 gamepad = SDL_OpenGamepad(
id);
3484 if (gamepad ==
nullptr) {
3485 logEnv(std::format(
"failed to open gamepad id={}",
static_cast<int>(
id)));
3489 const char *padName = SDL_GetGamepadName(gamepad);
3490 logEnv(std::format(
"gamepad connected: id={} name='{}'",
3491 static_cast<int>(
id),
3492 padName !=
nullptr ? padName :
"unknown"));
3496 void tryOpenFirstGamepad() {
3497 if (gamepad !=
nullptr) {
3501 SDL_JoystickID *ids = SDL_GetGamepads(&count);
3502 if (ids ==
nullptr || count <= 0) {
3503 if (ids !=
nullptr) {
3508 openGamepad(ids[0]);
3512 [[nodiscard]]
static glm::mat4 composeNormalizedModel(
const mxvk::VKAbstractModel &model,
const glm::mat4 &world) {
3513 glm::mat4 transform = world;
3514 transform = transform * glm::scale(glm::mat4(1.0f), glm::vec3(model.
modelRenderScale()));
3515 transform = transform * glm::translate(glm::mat4(1.0f), model.
modelCenterOffset());
3522 [[nodiscard]]
static glm::mat4 composeRecenteredModel(
const mxvk::VKAbstractModel &model,
const glm::mat4 &world) {
3526 void renderModel(VkCommandBuffer cmd,
3527 uint32_t imageIndex,
3528 mxvk::VKAbstractModel &model,
3529 const glm::mat4 &world,
3530 const glm::mat4 &view,
3531 const glm::mat4 &proj,
3532 const glm::vec4 &fx,
3533 bool autoNormalize =
true) {
3534 mxvk::UniformBufferObject ubo{};
3535 ubo.
model = autoNormalize ? composeNormalizedModel(model, world)
3536 : composeRecenteredModel(model, world);
3541 model.
render(cmd, imageIndex,
false);
3544 void renderRawModel(VkCommandBuffer cmd,
3545 uint32_t imageIndex,
3546 mxvk::VKAbstractModel &model,
3547 const glm::mat4 &world,
3548 const glm::mat4 &view,
3549 const glm::mat4 &proj,
3550 const glm::vec4 &fx) {
3551 mxvk::UniformBufferObject ubo{};
3557 model.
render(cmd, imageIndex,
false);
3560 void updateCameraVectors() {
3561 glm::vec3 front(0.0f);
3562 front.x = std::cos(glm::radians(yaw)) * std::cos(glm::radians(pitch));
3563 front.y = std::sin(glm::radians(pitch));
3564 front.z = std::sin(glm::radians(yaw)) * std::cos(glm::radians(pitch));
3565 cameraFront = glm::normalize(front);
3568 void buildCameraBasis(glm::vec3 &forward, glm::vec3 &right, glm::vec3 &up)
const {
3569 forward = cameraFront;
3570 if (glm::length(forward) <= 1e-5f) {
3571 forward = glm::vec3(0.0f, 0.0f, -1.0f);
3573 forward = glm::normalize(forward);
3576 right = glm::cross(forward, glm::vec3(0.0f, 1.0f, 0.0f));
3577 if (glm::length(right) <= 1e-5f) {
3578 right = glm::vec3(1.0f, 0.0f, 0.0f);
3580 right = glm::normalize(right);
3583 up = glm::cross(right, forward);
3584 if (glm::length(up) <= 1e-5f) {
3585 up = glm::vec3(0.0f, 1.0f, 0.0f);
3587 up = glm::normalize(up);
3591 [[nodiscard]] glm::vec3 blasterMuzzleTipPosition()
const {
3592 glm::vec3 forward(0.0f);
3593 glm::vec3 right(0.0f);
3595 buildCameraBasis(forward, right, up);
3596 return cameraPos + (forward * 0.55f) + (right * 0.18f) - (up * 0.12f);
3599 [[nodiscard]] glm::vec3 projectileSpawnPosition()
const {
3600 glm::vec3 forward(0.0f);
3601 glm::vec3 right(0.0f);
3603 buildCameraBasis(forward, right, up);
3604 constexpr float projectileForwardOffset = 0.015f;
3605 return blasterMuzzleTipPosition() + (forward * projectileForwardOffset);
3608 [[nodiscard]] glm::mat4 blasterWorldTransform()
const {
3609 glm::vec3 forward(0.0f);
3610 glm::vec3 right(0.0f);
3612 buildCameraBasis(forward, right, up);
3614 constexpr float blasterScale = 0.45f;
3615 constexpr glm::vec3 localMuzzle(0.95f, 0.09f, 0.0f);
3616 const glm::vec3 desiredMuzzle = blasterMuzzleTipPosition();
3617 const glm::vec3 origin = desiredMuzzle - (forward * (localMuzzle.x * blasterScale)) - (up * (localMuzzle.y * blasterScale)) - (right * (localMuzzle.z * blasterScale));
3619 glm::mat4 world(1.0f);
3620 world[0] = glm::vec4(forward * blasterScale, 0.0f);
3621 world[1] = glm::vec4(up * blasterScale, 0.0f);
3622 world[2] = glm::vec4(right * blasterScale, 0.0f);
3623 world[3] = glm::vec4(origin, 1.0f);
3627 [[nodiscard]]
float viewDistanceInDirection(
const glm::vec3 &origin,
const glm::vec3 &direction)
const {
3628 const glm::vec3 dir = glm::normalize(glm::vec3(direction.x, 0.0f, direction.z));
3629 constexpr float maxDistance = 14.0f;
3630 constexpr float step = 0.35f;
3631 constexpr float probeRadius = 0.30f;
3632 for (
float d = step; d <= maxDistance; d += step) {
3633 const glm::vec3 point = origin + (dir * d);
3634 if (world.checkWallCollision(point, probeRadius) || world.checkPillarCollision(point, probeRadius)) {
3641 [[nodiscard]]
float chooseBestSpawnYaw(
const glm::vec3 &origin)
const {
3642 constexpr float pi = 3.14159265358979323846f;
3643 constexpr int sampleCount = 48;
3644 float bestDistance = -1.0f;
3645 float bestYaw = yaw;
3646 for (
int i = 0; i < sampleCount; ++i) {
3647 const float angle = (-pi) + (2.0f * pi *
static_cast<float>(i) /
static_cast<float>(sampleCount));
3648 const glm::vec3 dir(std::cos(angle), 0.0f, std::sin(angle));
3649 const float dist = viewDistanceInDirection(origin, dir);
3650 if (dist > bestDistance) {
3651 bestDistance = dist;
3652 bestYaw = glm::degrees(angle);
3658 void updatePlayer(
float deltaTime) {
3659 const bool *keys = SDL_GetKeyboardState(
nullptr);
3660 glm::vec3 horizontalFront = glm::normalize(glm::vec3(cameraFront.x, 0.0f, cameraFront.z));
3661 if (glm::length(horizontalFront) < 0.0001f) {
3662 horizontalFront = glm::vec3(0.0f, 0.0f, -1.0f);
3664 const glm::vec3 right = glm::normalize(glm::cross(horizontalFront, glm::vec3(0.0f, 1.0f, 0.0f)));
3666 glm::vec3 desired = cameraPos;
3667 bool sprint = keys[SDL_SCANCODE_LSHIFT] != 0;
3668 if (gamepad !=
nullptr && SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_LEFT_STICK)) {
3671 const float cameraSpeed = 0.2f;
3672 const float speed = sprint ? cameraSpeed * 2.0f : cameraSpeed;
3673 const float frameScale = deltaTime * 60.0f;
3674 const float moveStep = speed * frameScale;
3676 if (keys[SDL_SCANCODE_W]) {
3677 desired += horizontalFront * moveStep;
3679 if (keys[SDL_SCANCODE_S]) {
3680 desired -= horizontalFront * moveStep;
3682 if (keys[SDL_SCANCODE_A]) {
3683 desired -= right * moveStep;
3685 if (keys[SDL_SCANCODE_D]) {
3686 desired += right * moveStep;
3689 if (gamepad !=
nullptr) {
3690 const Sint16 leftX = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX);
3691 const Sint16 leftY = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY);
3692 if (std::abs(leftX) > stickDeadZone) {
3693 desired += moveStep * (
static_cast<float>(leftX) / 32768.0f) * right;
3695 if (std::abs(leftY) > stickDeadZone) {
3696 desired -= moveStep * (
static_cast<float>(leftY) / 32768.0f) * horizontalFront;
3699 const Sint16 rightX = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX);
3700 const Sint16 rightY = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY);
3701 if (std::abs(rightX) > stickDeadZone || std::abs(rightY) > stickDeadZone) {
3702 yaw += (
static_cast<float>(rightX) / 32768.0f) * controllerLookSensitivity;
3703 pitch -= (
static_cast<float>(rightY) / 32768.0f) * controllerLookSensitivity;
3704 pitch = glm::clamp(pitch, -89.0f, 89.0f);
3705 updateCameraVectors();
3709 constexpr float playerRadius = 0.5f;
3710 constexpr float cameraStandOff = 0.18f;
3711 const float collisionRadius = playerRadius + cameraStandOff;
3712 const auto isBlocked = [
this, collisionRadius](
const glm::vec3 &position) {
3713 return world.checkWallCollision(position, collisionRadius) || world.checkPillarCollision(position, collisionRadius);
3716 if (!isBlocked(desired)) {
3717 cameraPos = desired;
3721 glm::vec3 tryX = cameraPos;
3723 if (!isBlocked(tryX)) {
3724 cameraPos.x = tryX.x;
3727 glm::vec3 tryZ = cameraPos;
3729 if (!isBlocked(tryZ)) {
3730 cameraPos.z = tryZ.z;
3734 const bool crouch = keys[SDL_SCANCODE_LCTRL] != 0;
3735 const float minHeight = crouch ? 0.8f : 1.7f;
3736 if (keys[SDL_SCANCODE_SPACE] && cameraPos.y <= minHeight + 0.01f) {
3737 jumpVelocity = 0.3f;
3740 cameraPos.y += jumpVelocity * deltaTime * 60.0f;
3741 jumpVelocity -= gravity * deltaTime * 60.0f;
3742 if (cameraPos.y < minHeight) {
3743 cameraPos.y = minHeight;
3744 jumpVelocity = 0.0f;
3748 void fireProjectile() {
3749 emitMuzzleParticles();
3751 Projectile bullet{};
3752 bullet.position = projectileSpawnPosition();
3753 bullet.direction = glm::normalize(cameraFront);
3754 bullets.push_back(bullet);
3755 logEnv(std::format(
"projectile fired from ({:.2f}, {:.2f}, {:.2f}) dir=({:.2f}, {:.2f}, {:.2f}) active_bullets={}",
3765 void emitMuzzleParticles() {
3766 glm::vec3 forward(0.0f);
3767 glm::vec3 right(0.0f);
3769 buildCameraBasis(forward, right, up);
3771 const glm::vec3 muzzle = blasterMuzzleTipPosition();
3772 std::uniform_real_distribution<float> lateralJitter(-0.20f, 0.20f);
3773 std::uniform_real_distribution<float> verticalJitter(-0.12f, 0.12f);
3774 std::uniform_real_distribution<float> speedDist(8.0f, 26.0f);
3775 std::uniform_real_distribution<float> lifeDist(0.06f, 0.16f);
3776 std::uniform_real_distribution<float> warmDist(0.75f, 1.0f);
3778 constexpr int particleCount = 24;
3779 for (
int i = 0; i < particleCount; ++i) {
3780 ExplosionParticle p{};
3781 p.position = muzzle + (forward * 0.01f);
3783 glm::vec3 dir = forward + (right * lateralJitter(rng)) + (up * verticalJitter(rng));
3784 if (glm::length(dir) <= 1e-5f) {
3787 dir = glm::normalize(dir);
3790 const float speed = speedDist(rng);
3791 p.velocity = dir * speed;
3792 p.color = glm::vec3(warmDist(rng), warmDist(rng) * 0.7f, warmDist(rng) * 0.18f);
3793 p.maxLifetime = lifeDist(rng);
3794 p.size = 0.035f + (speed * 0.003f);
3795 explosionParticles.push_back(p);
3799 void updateProjectiles(
float deltaTime) {
3800 for (
size_t bulletIndex = 0; bulletIndex < bullets.size(); ++bulletIndex) {
3801 Projectile &bullet = bullets[bulletIndex];
3806 const glm::vec3 previous = bullet.position;
3807 const glm::vec3 displacement = bullet.direction * bullet.speed * deltaTime;
3808 bullet.position += displacement;
3810 bullet.distanceTraveled += glm::length(displacement);
3811 bullet.trailTimer += deltaTime;
3812 if (bullet.trailTimer >= 0.02f) {
3813 Projectile::TrailPoint point{};
3814 point.position = bullet.position;
3815 bullet.trail.push_back(point);
3816 bullet.trailTimer = 0.0f;
3818 for (Projectile::TrailPoint &point : bullet.trail) {
3819 point.lifetime += deltaTime;
3822 std::remove_if(bullet.trail.begin(), bullet.trail.end(), [](
const Projectile::TrailPoint &point) {
3823 return point.lifetime >= point.maxLifetime;
3825 bullet.trail.end());
3827 size_t collectibleIndex = 0;
3828 glm::vec3 collectibleImpact{0.0f};
3829 if (lineHitCollectible(previous, bullet.position, collectibleIndex, collectibleImpact)) {
3830 createExplosion(collectibleImpact, 5000,
false);
3831 const Collectible::Type hitType = world.collectibles()[collectibleIndex].type;
3832 const bool removed = deactivateCollectibleAt(collectibleIndex);
3833 resolveCollectibleClusters(2.0f, 3);
3838 logEnv(std::format(
"bullet {} hit {} collectible {} at ({:.2f}, {:.2f}, {:.2f}); destroyed={}",
3840 collectibleTypeName(hitType),
3842 collectibleImpact.x,
3843 collectibleImpact.y,
3844 collectibleImpact.z,
3849 const ProjectileTraceHit segmentHit = traceProjectileSegment(previous, bullet.position);
3850 if (segmentHit.type != ProjectileHitType::None) {
3852 if (segmentHit.type == ProjectileHitType::Floor) {
3853 createExplosion(glm::vec3(segmentHit.impact.x, 0.0f, segmentHit.impact.z), 1500,
true);
3855 logEnv(std::format(
"bullet {} hit floor at ({:.2f}, {:.2f}, {:.2f})",
3857 segmentHit.impact.x,
3859 segmentHit.impact.z));
3863 createExplosion(segmentHit.impact, 1500,
true);
3865 logEnv(std::format(
"bullet {} hit {} at ({:.2f}, {:.2f}, {:.2f})",
3867 (segmentHit.type == ProjectileHitType::Pillar) ?
"pillar" :
"wall",
3868 segmentHit.impact.x,
3869 segmentHit.impact.y,
3870 segmentHit.impact.z));
3874 if (bullet.
lifetime >= bullet.maxLifetime) {
3876 logEnv(std::format(
"bullet {} expired after {:.2f}s", bulletIndex, bullet.
lifetime));
3880 if (bullet.distanceTraveled >= bullet.maxDistance) {
3882 logEnv(std::format(
"bullet {} faded after traveling {:.2f} units", bulletIndex, bullet.distanceTraveled));
3886 bullets.erase(std::remove_if(bullets.begin(), bullets.end(), [](
const Projectile &b) { return !b.active; }), bullets.end());
3889 void updateCollectibles(
float deltaTime) {
3890 for (Collectible &obj : world.collectibles()) {
3894 obj.rotation.y += obj.rotationSpeed * deltaTime;
3895 if (obj.rotation.y > 360.0f) {
3896 obj.rotation.y -= 360.0f;
3900 collectibleClusterResolveTimer += deltaTime;
3901 if (collectibleClusterResolveTimer >= 0.75f) {
3902 collectibleClusterResolveTimer = 0.0f;
3903 resolveCollectibleClusters(2.0f, 2);
3907 void createExplosion(
const glm::vec3 &position,
int requestedCount,
bool isRed) {
3908 if (requestedCount <= 0) {
3912 constexpr float pi = 3.14159265358979323846f;
3913 std::uniform_real_distribution<float> speedDist(3.0f, 15.0f);
3914 std::uniform_real_distribution<float> angleDist(0.0f, 2.0f * pi);
3915 std::uniform_real_distribution<float> elevationDist(-(pi / 6.0f), pi / 3.0f);
3916 std::uniform_real_distribution<float> colorDist(0.7f, 1.0f);
3918 const int count = std::min(requestedCount * 2, 800);
3919 logEnv(std::format(
"explosion at ({:.2f}, {:.2f}, {:.2f}) particles={} style={}",
3924 isRed ?
"impact" :
"collectible"));
3925 for (
int i = 0; i < count; ++i) {
3926 ExplosionParticle p{};
3927 p.position = position;
3928 const float theta = angleDist(rng);
3929 const float phi = elevationDist(rng);
3930 const float v = speedDist(rng);
3931 p.velocity = glm::vec3(v * std::cos(phi) * std::cos(theta), v * std::sin(phi), v * std::cos(phi) * std::sin(theta));
3933 p.color = glm::vec3(colorDist(rng), colorDist(rng) * 0.3f, colorDist(rng) * 0.1f);
3935 p.color = glm::vec3(colorDist(rng), colorDist(rng) * 0.7f, colorDist(rng) * 0.2f);
3937 p.maxLifetime = 0.55f;
3938 p.size = 0.08f + (v * 0.010f);
3939 explosionParticles.push_back(p);
3943 void updateExplosions(
float deltaTime) {
3944 for (ExplosionParticle &particle : explosionParticles) {
3945 if (!particle.active) {
3948 particle.position += particle.velocity * deltaTime;
3949 particle.velocity.y -= 9.8f * deltaTime;
3951 for (
const PillarInstance &pillar : world.pillars()) {
3952 const glm::vec2 particle2d(particle.position.x, particle.position.z);
3953 const glm::vec2 pillar2d(pillar.position.x, pillar.position.z);
3954 const float distance = glm::length(particle2d - pillar2d);
3955 if (distance < pillar.radius && particle.position.y > 0.0f && particle.position.y < pillar.height) {
3956 glm::vec2 normal(1.0f, 0.0f);
3957 if (distance > 0.00001f) {
3958 normal = glm::normalize(particle2d - pillar2d);
3960 const glm::vec2 vel2d(particle.velocity.x, particle.velocity.z);
3961 const glm::vec2 reflected = vel2d - 2.0f * glm::dot(vel2d, normal) * normal;
3962 particle.velocity.x = reflected.x * 0.5f;
3963 particle.velocity.z = reflected.y * 0.5f;
3964 const glm::vec2 correction = normal * (pillar.radius - distance + 0.1f);
3965 particle.position.x += correction.x;
3966 particle.position.z += correction.y;
3970 for (
const WallSegment &wall : world.walls()) {
3971 glm::vec3 wallDir = wall.end - wall.start;
3972 const float wallLength = glm::length(wallDir);
3973 if (wallLength < 0.0001f) {
3976 wallDir = glm::normalize(wallDir);
3977 const glm::vec3 toStart = particle.position - wall.start;
3978 float projection = glm::dot(toStart, wallDir);
3979 projection = glm::clamp(projection, 0.0f, wallLength);
3980 glm::vec3 closest = wall.start + wallDir * projection;
3981 closest.y = particle.position.y;
3982 const float distance = glm::length(particle.position - closest);
3983 if (distance < 0.5f && particle.position.y >= 0.0f && particle.position.y <= wall.height) {
3984 glm::vec3 normal(1.0f, 0.0f, 0.0f);
3985 if (distance > 0.0001f) {
3986 normal = glm::normalize(particle.position - closest);
3988 particle.velocity = glm::reflect(particle.velocity, normal) * 0.5f;
3989 particle.position += normal * 0.2f;
3993 if (particle.position.y < 0.0f) {
3994 particle.position.y = 0.0f;
3995 particle.velocity.y = -particle.velocity.y * 0.3f;
3996 particle.velocity.x *= 0.8f;
3997 particle.velocity.z *= 0.8f;
4000 particle.lifetime += deltaTime;
4001 particle.size *= 0.98f;
4002 if (particle.lifetime >= particle.maxLifetime) {
4003 particle.active =
false;
4007 explosionParticles.erase(
4008 std::remove_if(explosionParticles.begin(), explosionParticles.end(), [](
const ExplosionParticle &p) {
4011 explosionParticles.end());
4014 [[nodiscard]]
bool lineHitWall(
const glm::vec3 &from,
const glm::vec3 &to, glm::vec3 &impactOut)
const {
4015 const glm::vec3 dir = to - from;
4016 constexpr float bulletRadius = 0.015f;
4017 const float travel = glm::length(dir);
4018 if (travel <= 1e-8f) {
4022 constexpr float sampleStride = 0.05f;
4023 const int steps = std::max(1,
static_cast<int>(std::ceil(travel / sampleStride)));
4024 float previousT = 0.0f;
4025 for (
int i = 0; i <= steps; ++i) {
4026 const float t =
static_cast<float>(i) /
static_cast<float>(steps);
4027 const glm::vec3 point = from + (dir * t);
4028 if (pointHitsWall3D(point, bulletRadius)) {
4029 float lo = previousT;
4031 for (
int iter = 0; iter < 10; ++iter) {
4032 const float mid = 0.5f * (lo + hi);
4033 const glm::vec3 midPoint = from + (dir * mid);
4034 if (pointHitsWall3D(midPoint, bulletRadius)) {
4040 impactOut = from + (dir * hi);
4048 [[nodiscard]]
bool lineHitPillar(
const glm::vec3 &from,
const glm::vec3 &to, glm::vec3 &impactOut)
const {
4049 const glm::vec3 dir = to - from;
4050 constexpr float bulletRadius = 0.015f;
4051 const float travel = glm::length(dir);
4052 if (travel <= 1e-8f) {
4056 constexpr float sampleStride = 0.05f;
4057 const int steps = std::max(1,
static_cast<int>(std::ceil(travel / sampleStride)));
4058 float previousT = 0.0f;
4059 for (
int i = 0; i <= steps; ++i) {
4060 const float t =
static_cast<float>(i) /
static_cast<float>(steps);
4061 const glm::vec3 point = from + (dir * t);
4062 if (pointHitsPillar3D(point, bulletRadius)) {
4063 float lo = previousT;
4065 for (
int iter = 0; iter < 10; ++iter) {
4066 const float mid = 0.5f * (lo + hi);
4067 const glm::vec3 midPoint = from + (dir * mid);
4068 if (pointHitsPillar3D(midPoint, bulletRadius)) {
4074 impactOut = from + (dir * hi);
4082 [[nodiscard]]
bool lineHitCollectible(
const glm::vec3 &from,
const glm::vec3 &to,
size_t &indexOut, glm::vec3 &impactOut)
const {
4083 const glm::vec3 dir = to - from;
4084 const float dirLen2 = glm::dot(dir, dir);
4085 constexpr float bulletRadius = 0.015f;
4086 if (dirLen2 <= 1e-8f) {
4092 size_t bestIndex = 0;
4094 const std::vector<Collectible> &collectibles = world.collectibles();
4095 for (
size_t i = 0; i < collectibles.size(); ++i) {
4096 const Collectible &obj = collectibles[i];
4104 if (obj.type == Collectible::Type::Bird) {
4105 const glm::vec3 halfExtents(obj.radius + bulletRadius);
4106 const glm::vec3 center = obj.position + obj.hitCenterOffset;
4107 const glm::vec3 boxMin = center - halfExtents;
4108 const glm::vec3 boxMax = center + halfExtents;
4112 bool slabMiss =
false;
4114 for (
int axis = 0; axis < 3; ++axis) {
4115 const float origin = from[axis];
4116 const float delta = dir[axis];
4117 const float minB = boxMin[axis];
4118 const float maxB = boxMax[axis];
4120 if (std::abs(delta) <= 1e-8f) {
4121 if (origin < minB || origin > maxB) {
4128 float t0 = (minB - origin) / delta;
4129 float t1 = (maxB - origin) / delta;
4134 tMin = std::max(tMin, t0);
4135 tMax = std::min(tMax, t1);
4147 const glm::vec3 center = obj.position + obj.hitCenterOffset;
4148 const glm::vec3 m = from - center;
4149 const float a = dirLen2;
4150 const float b = 2.0f * glm::dot(m, dir);
4151 const float hitRadius = obj.radius + bulletRadius;
4152 const float c = glm::dot(m, m) - (hitRadius * hitRadius);
4153 const float discriminant = (b * b) - (4.0f * a * c);
4154 if (discriminant >= 0.0f) {
4155 const float sqrtD = std::sqrt(discriminant);
4156 const float invDen = 1.0f / (2.0f * a);
4157 const float t0 = (-b - sqrtD) * invDen;
4158 const float t1 = (-b + sqrtD) * invDen;
4159 if (t0 >= 0.0f && t0 <= 1.0f) {
4162 }
else if (t1 >= 0.0f && t1 <= 1.0f) {
4169 if (hit && tHit >= 0.0f && tHit <= 1.0f && tHit < bestT) {
4180 indexOut = bestIndex;
4181 impactOut = from + (dir * bestT);
4185 [[nodiscard]]
bool pointHitsWall3D(
const glm::vec3 &point,
float radius)
const {
4186 const float halfThickness = (world.wallThickness() * 0.5f) + radius;
4187 const float halfThicknessSq = halfThickness * halfThickness;
4188 for (
const WallSegment &wall : world.walls()) {
4189 if (point.y < 0.0f || point.y > wall.height) {
4193 const glm::vec2 start(wall.start.x, wall.start.z);
4194 const glm::vec2 end(wall.end.x, wall.end.z);
4195 const glm::vec2 seg = end - start;
4196 const float segLen2 = glm::dot(seg, seg);
4197 if (segLen2 <= 1e-8f) {
4201 const glm::vec2 p(point.x, point.z);
4202 const glm::vec2 toPoint = p - start;
4203 const float t = glm::clamp(glm::dot(toPoint, seg) / segLen2, 0.0f, 1.0f);
4204 const glm::vec2 closest = start + (seg * t);
4205 const glm::vec2 d = p - closest;
4206 if (glm::dot(d, d) <= halfThicknessSq) {
4213 [[nodiscard]]
bool pointHitsPillar3D(
const glm::vec3 &point,
float radius)
const {
4214 for (
const PillarInstance &pillar : world.pillars()) {
4215 if (point.y < 0.0f || point.y > pillar.height) {
4219 const glm::vec2 p(point.x, point.z);
4220 const glm::vec2 c(pillar.position.x, pillar.position.z);
4221 const float hitRadius = pillar.radius + radius;
4222 const glm::vec2 d = p - c;
4223 if (glm::dot(d, d) <= (hitRadius * hitRadius)) {
4230 [[nodiscard]]
float birdGroundYForScale(
float scale)
const {
4231 const glm::vec3 extent = birdModel.modelAxisExtent();
4232 const glm::vec3 centerOffset = birdModel.modelCenterOffset();
4233 const float modelMinY = -centerOffset.y - (extent.y * 0.5f);
4234 const float clampedScale = std::max(scale, 0.0001f);
4235 return std::max(0.0f, -modelMinY * clampedScale);
4238 [[nodiscard]]
float birdHitHalfSideForScale(
float scale)
const {
4239 const glm::vec3 extent = birdModel.modelAxisExtent();
4240 const float modelSide = std::max({extent.x, extent.y, extent.z, 0.0001f});
4241 const float clampedScale = std::max(scale, 0.0001f);
4242 return 0.5f * modelSide * clampedScale;
4245 [[nodiscard]]
float saturnHitRadiusForScale(
float scale)
const {
4246 const glm::vec3 extent = saturnModel.modelAxisExtent();
4247 const float modelDiameter = std::max({extent.x, extent.y, extent.z, 0.0001f});
4248 const float clampedScale = std::max(scale, 0.0001f);
4249 return 0.5f * modelDiameter * clampedScale;
4252 [[nodiscard]] glm::vec3 saturnHitCenterOffsetForScale(
float scale)
const {
4253 const glm::vec3 centerOffset = saturnModel.modelCenterOffset();
4254 const float clampedScale = std::max(scale, 0.0001f);
4255 return glm::vec3(-centerOffset.x * clampedScale,
4256 -centerOffset.y * clampedScale,
4257 -centerOffset.z * clampedScale);
4260 [[nodiscard]] glm::vec3 birdHitCenterOffsetForScale(
float scale)
const {
4261 const glm::vec3 centerOffset = birdModel.modelCenterOffset();
4262 const float clampedScale = std::max(scale, 0.0001f);
4263 return glm::vec3(0.0f, -centerOffset.y * clampedScale, 0.0f);
4266 [[nodiscard]]
float birdSpawnClearanceRadiusForScale(
float scale)
const {
4267 const glm::vec3 extent = birdModel.modelAxisExtent();
4268 const glm::vec3 centerOffset = birdModel.modelCenterOffset();
4269 const float clampedScale = std::max(scale, 0.0001f);
4271 const float halfXFromOrigin = (extent.x * 0.5f) + std::abs(centerOffset.x);
4272 const float halfZFromOrigin = (extent.z * 0.5f) + std::abs(centerOffset.z);
4273 const float horizontalRadius = std::max(halfXFromOrigin, halfZFromOrigin) * clampedScale;
4274 return horizontalRadius + 0.05f;
4277 [[nodiscard]]
float placementRadiusForCollectible(
const Collectible &obj)
const {
4278 if (obj.type == Collectible::Type::Bird) {
4279 return std::max(obj.radius, birdSpawnClearanceRadiusForScale(obj.scale.x));
4284 void normalizeCollectiblesToModel() {
4285 for (Collectible &obj : world.collectibles()) {
4286 if (obj.type == Collectible::Type::Bird) {
4287 obj.radius = birdHitHalfSideForScale(obj.scale.x);
4288 obj.hitCenterOffset = birdHitCenterOffsetForScale(obj.scale.x);
4289 obj.position.y = birdGroundYForScale(obj.scale.x);
4291 obj.radius = saturnHitRadiusForScale(obj.scale.x);
4292 obj.hitCenterOffset = saturnHitCenterOffsetForScale(obj.scale.x);
4296 resolveCollectibleEnvironmentCollisions();
4297 resolveCollectibleOverlaps();
4298 resolveCollectibleClusters(2.0f, 5);
4301 [[nodiscard]]
bool overlapsCollectibleAt(
const glm::vec3 &candidate,
4304 bool includeInactive)
const {
4305 const std::vector<Collectible> &collectibles = world.collectibles();
4306 for (
size_t i = 0; i < collectibles.size(); ++i) {
4307 if (i == ignoreIndex) {
4310 const Collectible &other = collectibles[i];
4311 if (!includeInactive && !other.active) {
4315 const float separation = std::max(5.0f, other.radius + radius + 0.2f);
4316 if (glm::length(other.position - candidate) < separation) {
4323 bool relocateCollectible(
size_t index,
float minMoveDistance,
int maxAttempts) {
4324 std::vector<Collectible> &collectibles = world.collectibles();
4325 if (index >= collectibles.size()) {
4329 Collectible &obj = collectibles[index];
4330 const glm::vec3 oldPosition = obj.position;
4331 const float y = (obj.type == Collectible::Type::Bird) ? birdGroundYForScale(obj.scale.x) : 2.5f;
4332 const float placementRadius = placementRadiusForCollectible(obj);
4334 for (
int attempt = 0; attempt < maxAttempts; ++attempt) {
4335 glm::vec3 candidate{};
4336 if (!sampleNavigablePoint(y, placementRadius, candidate, 4)) {
4339 if (glm::length(candidate - oldPosition) < minMoveDistance) {
4342 if (overlapsCollectibleAt(candidate, obj.radius, index,
false)) {
4346 obj.position = candidate;
4353 void resolveCollectibleEnvironmentCollisions() {
4354 std::vector<Collectible> &collectibles = world.collectibles();
4355 for (
size_t i = 0; i < collectibles.size(); ++i) {
4356 if (!collectibles[i].
active) {
4360 const float placementRadius = placementRadiusForCollectible(collectibles[i]);
4361 if (!world.checkWallCollision(collectibles[i].position, placementRadius) &&
4362 !world.checkPillarCollision(collectibles[i].position, placementRadius)) {
4366 relocateCollectible(i, 2.0f, 512);
4370 void resolveCollectibleOverlaps() {
4371 std::vector<Collectible> &collectibles = world.collectibles();
4372 for (
size_t i = 0; i < collectibles.size(); ++i) {
4373 if (!collectibles[i].
active) {
4376 if (!overlapsCollectibleAt(collectibles[i].position, collectibles[i].radius, i,
false)) {
4379 relocateCollectible(i, 1.5f, 320);
4383 void resolveCollectibleClusters(
float minVisualSeparation,
int passes) {
4384 if (minVisualSeparation <= 0.0f || passes <= 0) {
4388 std::vector<Collectible> &collectibles = world.collectibles();
4389 const float minVisualSeparationSq = minVisualSeparation * minVisualSeparation;
4390 for (
int pass = 0; pass < passes; ++pass) {
4391 bool movedAny =
false;
4392 for (
size_t i = 0; i < collectibles.size(); ++i) {
4393 if (!collectibles[i].
active) {
4397 for (
size_t j = i + 1; j < collectibles.size(); ++j) {
4398 if (!collectibles[j].
active) {
4402 const glm::vec3 delta = collectibles[j].position - collectibles[i].position;
4403 if (glm::dot(delta, delta) >= minVisualSeparationSq) {
4407 if (relocateCollectible(j, minVisualSeparation, 512)) {
4419 void disperseNearbyCollectibles(
const glm::vec3 ¢er,
float radius,
size_t ignoreIndex) {
4420 std::vector<Collectible> &collectibles = world.collectibles();
4421 for (
size_t i = 0; i < collectibles.size(); ++i) {
4422 if (i == ignoreIndex) {
4425 if (!collectibles[i].
active) {
4428 if (glm::length(collectibles[i].position - center) > radius) {
4432 relocateCollectible(i, std::max(3.0f, radius), 256);
4436 [[nodiscard]]
bool deactivateCollectibleAt(
size_t index) {
4437 std::vector<Collectible> &collectibles = world.collectibles();
4438 if (index >= collectibles.size()) {
4442 Collectible &obj = collectibles[index];
4451 std::string assetRoot;
4452 std::string shaderRoot;
4453 std::string modelRoot;
4456 mxvk::VKAbstractModel floorModel{};
4457 RawWallRenderer rawWallRenderer{};
4458 RawPillarRenderer rawPillarRenderer{};
4459 mxvk::VKAbstractModel saturnModel{};
4460 mxvk::VKAbstractModel birdModel{};
4461 mxvk::VKAbstractModel blasterModel{};
4462 mxvk::VKAbstractModel bulletModel{};
4464 VkPipelineLayout pointPipelineLayout = VK_NULL_HANDLE;
4465 VkPipeline pointPipeline = VK_NULL_HANDLE;
4466 VkBuffer pointVertexBuffer = VK_NULL_HANDLE;
4467 VkDeviceMemory pointVertexMemory = VK_NULL_HANDLE;
4468 void *pointVertexMapped =
nullptr;
4469 size_t maxPointVertices = 200000;
4470 std::string pointParticleVertSpv{};
4471 std::string pointParticleFragSpv{};
4472 std::string modelVertSpv{};
4473 std::string pillarVertSpv{};
4474 std::string wallFragSpv{};
4475 std::string floorFragSpv{};
4476 std::string pillarFragSpv{};
4477 std::string objectFragSpv{};
4478 std::string bulletFragSpv{};
4480 std::vector<Projectile> bullets{};
4481 std::vector<ExplosionParticle> explosionParticles{};
4482 std::mt19937
rng{std::random_device{}()};
4484 glm::vec3 cameraPos{0.0f, 1.7f, 0.0f};
4485 glm::vec3 cameraFront{0.0f, 0.0f, -1.0f};
4488 bool mouseCapture =
true;
4489 bool firstMouse =
true;
4490 bool suppressProjectileOnNextLeftDown =
false;
4491 bool showFps =
true;
4492 float mouseSensitivity = 0.15f;
4494 float jumpVelocity = 0.0f;
4495 float gravity = 0.015f;
4496 float collectibleClusterResolveTimer = 0.0f;
4497 uint32_t destroyedCount = 0;
4499 SDL_Gamepad *gamepad =
nullptr;
4500 SDL_JoystickID gamepadId = 0;
4501 int stickDeadZone = 8000;
4502 float controllerLookSensitivity = 2.0f;
4504 std::chrono::steady_clock::time_point lastTick{std::chrono::steady_clock::now()};