Index: gl-shaders/explosion.vert
===================================================================
--- gl-shaders/explosion.vert	(revision 73a10caea6c4ac9731862d7f1ed503afd065643b)
+++ gl-shaders/explosion.vert	(revision 845a2cb7a34c5349a49ed51c2b9111b3eba65572)
@@ -19,4 +19,5 @@
 
 void main() {
+   /*
    float duration = 0.5;
    float t = cur_time - explosion_start_time[ubo_index] - start_time;
@@ -36,9 +37,12 @@
    }
 
-   vec3 p = vec3(0.0, 0.0, 0.0); //  this is the center of the explosion
-   vec3 a = vec3(0.0, 0.1, 0.0);
-   p += normalize(v_i) * mod(t, duration) / duration * 0.3; // allow time to loop around so particle emitter keeps going
+   //  this is the center of the explosion
+   // allow time to loop around so particle emitter keeps going
+   vec3 p = normalize(v_i) * mod(t, duration) / duration * 0.3;
 
    gl_Position = proj * view * model_mats[ubo_index] * vec4(p, 1.0);
-   gl_PointSize = 15.0; // size in pixels
+   */
+   opacity = 1.0;
+   gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
+   gl_PointSize = 100.0; // size in pixels
 }
Index: graphics-pipeline_vulkan.hpp
===================================================================
--- graphics-pipeline_vulkan.hpp	(revision 73a10caea6c4ac9731862d7f1ed503afd065643b)
+++ graphics-pipeline_vulkan.hpp	(revision 845a2cb7a34c5349a49ed51c2b9111b3eba65572)
@@ -49,6 +49,6 @@
       // if it will never change, just pass it in the constructor and save it
       // If it does change, I could add an updateSwapchainImageCount() function
-      GraphicsPipeline_Vulkan(VkPhysicalDevice physicalDevice, VkDevice device, VkRenderPass renderPass,
-         Viewport viewport, vector<VkImage>& swapChainImages,
+      GraphicsPipeline_Vulkan(VkPrimitiveTopology topology, VkPhysicalDevice physicalDevice, VkDevice device,
+         VkRenderPass renderPass, Viewport viewport, vector<VkImage>& swapChainImages,
          size_t vertexCapacity, size_t indexCapacity, size_t objectCapacity);
       ~GraphicsPipeline_Vulkan();
@@ -86,4 +86,5 @@
    
    private:
+      VkPrimitiveTopology topology;
       VkPhysicalDevice physicalDevice;
       VkDevice device;
@@ -136,7 +137,8 @@
 template<class VertexType, class SSBOType>
 GraphicsPipeline_Vulkan<VertexType, SSBOType>::GraphicsPipeline_Vulkan(
-      VkPhysicalDevice physicalDevice, VkDevice device,
+      VkPrimitiveTopology topology, VkPhysicalDevice physicalDevice, VkDevice device,
       VkRenderPass renderPass, Viewport viewport, vector<VkImage>& swapChainImages,
       size_t vertexCapacity, size_t indexCapacity, size_t objectCapacity) {
+   this->topology = topology;
    this->physicalDevice = physicalDevice;
    this->device = device;
@@ -274,5 +276,5 @@
    VkPipelineInputAssemblyStateCreateInfo inputAssembly = {};
    inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
-   inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+   inputAssembly.topology = this->topology;
    inputAssembly.primitiveRestartEnable = VK_FALSE;
 
Index: main-vulkan.cpp
===================================================================
--- main-vulkan.cpp	(revision 73a10caea6c4ac9731862d7f1ed503afd065643b)
+++ main-vulkan.cpp	(revision 845a2cb7a34c5349a49ed51c2b9111b3eba65572)
@@ -25,5 +25,5 @@
 
    try {
-      game.run(800, 600, GUI_FLAGS_WINDOW_FULLSCREEN);
+      game.run(800, 600, 0);
    } catch (const exception& e) {
       cerr << e.what() << endl;
Index: new-game.cpp
===================================================================
--- new-game.cpp	(revision 73a10caea6c4ac9731862d7f1ed503afd065643b)
+++ new-game.cpp	(revision 845a2cb7a34c5349a49ed51c2b9111b3eba65572)
@@ -254,6 +254,8 @@
 
 const int KEY_STATE_UNCHANGED = -1;
+/*** START OF REFACTORED CODE ***/
 const bool FULLSCREEN = false;
 const int EXPLOSION_PARTICLE_COUNT = 300;
+/*** END OF REFACTORED CODE ***/
 unsigned int MAX_UNIFORMS = 0; // Requires OpenGL constants only available at runtime, so it can't be const
 
@@ -262,6 +264,6 @@
 
 /*** START OF REFACTORED CODE ***/
-int windowWidth = 640;
-int windowHeight = 480;
+int windowWidth = 800;
+int windowHeight = 600;
 
 vec3 cam_pos;
Index: shaders/explosion.frag
===================================================================
--- shaders/explosion.frag	(revision 845a2cb7a34c5349a49ed51c2b9111b3eba65572)
+++ shaders/explosion.frag	(revision 845a2cb7a34c5349a49ed51c2b9111b3eba65572)
@@ -0,0 +1,10 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in float opacity;
+
+layout(location = 0) out vec4 frag_color;
+
+void main() {
+   frag_color = vec4(1.0, opacity * opacity, 0.0, opacity);
+}
Index: shaders/explosion.vert
===================================================================
--- shaders/explosion.vert	(revision 845a2cb7a34c5349a49ed51c2b9111b3eba65572)
+++ shaders/explosion.vert	(revision 845a2cb7a34c5349a49ed51c2b9111b3eba65572)
@@ -0,0 +1,65 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+struct Object {
+   mat4 model;
+   float explosion_start_time;
+   float explosion_duration;
+   bool deleted;
+};
+
+layout (binding = 0) uniform UniformBufferObject {
+   mat4 view;
+   mat4 proj;
+   float cur_time;
+} ubo;
+
+layout(binding = 1) readonly buffer StorageBufferObject {
+   Object objects[];
+} sbo;
+
+layout(location = 0) in vec3 particle_start_velocity;
+layout(location = 1) in float particle_start_time;
+layout(location = 2) in uint obj_index;
+
+layout(location = 0) out float opacity;
+
+void main() {
+   /*
+   mat4 model = sbo.objects[obj_index].model;
+   float explosion_start_time = sbo.objects[obj_index].explosion_start_time;
+   float explosion_duration = sbo.objects[obj_index].explosion_duration;
+   bool deleted = sbo.objects[obj_index].deleted;
+
+   float t = ubo.cur_time - explosion_start_time - particle_start_time;
+
+   if (t < 0.0) {
+      opacity = 0.0;
+   } else {
+      // Need to find out the last time this particle was at the origin
+      // If that is greater than the duration, hide the particle
+      float cur = floor(t / explosion_duration);
+      float end = floor((explosion_duration - particle_start_time) / explosion_duration);
+      if (cur > end) {
+         opacity = 0.0;
+      } else {
+         opacity = 1.0 - (t / explosion_duration);
+      }
+   }
+
+   if (deleted) {
+      gl_Position = vec4(0.0, 0.0, 2.0, 1.0);
+   } else {
+      //  this is the center of the explosion
+      vec3 p = vec3(0.0, 0.0, 0.0);
+
+      // allow time to loop around so particle emitter keeps going
+      p += normalize(particle_start_velocity) * mod(t, explosion_duration) / explosion_duration * 0.3;
+
+      gl_Position = ubo.proj * ubo.view * model * vec4(p, 1.0);
+   }
+   */
+   opacity = 1.0;
+   gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
+   gl_PointSize = 100.0; // size in pixels
+}
Index: vulkan-game.cpp
===================================================================
--- vulkan-game.cpp	(revision 73a10caea6c4ac9731862d7f1ed503afd065643b)
+++ vulkan-game.cpp	(revision 845a2cb7a34c5349a49ed51c2b9111b3eba65572)
@@ -3,4 +3,5 @@
 #include <array>
 #include <iostream>
+#include <numeric>
 #include <set>
 
@@ -28,4 +29,5 @@
    this->asteroid_VP_mats = {};
    this->laser_VP_mats = {};
+   this->explosion_UBO = {};
 }
 
@@ -519,4 +521,9 @@
       }, false);
 
+   ship.model_base =
+      translate(mat4(1.0f), vec3(0.0f, -1.2f, 1.65f)) *
+      scale(mat4(1.0f), vec3(0.1f, 0.1f, 0.1f));
+   ship.modified = true;
+
    shipPipeline.createDescriptorSetLayout();
    shipPipeline.createPipeline("shaders/ship-vert.spv", "shaders/ship-frag.spv");
@@ -559,4 +566,20 @@
    laserPipeline.createDescriptorSets(swapChainImages);
 
+   explosionPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ExplosionVertex::particleStartVelocity));
+   explosionPipeline.addAttribute(VK_FORMAT_R32_SFLOAT, offset_of(&ExplosionVertex::particleStartTime));
+   explosionPipeline.addAttribute(VK_FORMAT_R32_UINT, offset_of(&ExplosionVertex::objIndex));
+
+   createBufferSet(sizeof(UBO_Explosion), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+      uniformBuffers_explosionPipeline, uniformBuffersMemory_explosionPipeline, uniformBufferInfoList_explosionPipeline);
+
+   explosionPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+      VK_SHADER_STAGE_VERTEX_BIT, &uniformBufferInfoList_explosionPipeline);
+   explosionPipeline.addStorageDescriptor(VK_SHADER_STAGE_VERTEX_BIT);
+
+   explosionPipeline.createDescriptorSetLayout();
+   explosionPipeline.createPipeline("shaders/explosion-vert.spv", "shaders/explosion-frag.spv");
+   explosionPipeline.createDescriptorPool(swapChainImages);
+   explosionPipeline.createDescriptorSets(swapChainImages);
+
    cout << "Created all the graphics pipelines" << endl;
 
@@ -564,26 +587,31 @@
 
    createSyncObjects();
-
-   ship.model_base =
-      translate(mat4(1.0f), vec3(0.0f, -1.2f, 1.65f)) *
-      scale(mat4(1.0f), vec3(0.1f, 0.1f, 0.1f));
-   ship.modified = true;
 }
 
 void VulkanGame::initGraphicsPipelines() {
-   overlayPipeline = GraphicsPipeline_Vulkan<OverlayVertex, void*>(physicalDevice, device, renderPass,
+   overlayPipeline = GraphicsPipeline_Vulkan<OverlayVertex, void*>(
+      VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass,
       { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 4, 6, 0);
 
-   modelPipeline = GraphicsPipeline_Vulkan<ModelVertex, SSBO_ModelObject>(physicalDevice, device, renderPass,
+   modelPipeline = GraphicsPipeline_Vulkan<ModelVertex, SSBO_ModelObject>(
+      VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass,
       { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 16, 24, 10);
 
-   shipPipeline = GraphicsPipeline_Vulkan<ShipVertex, SSBO_ModelObject>(physicalDevice, device, renderPass,
+   shipPipeline = GraphicsPipeline_Vulkan<ShipVertex, SSBO_ModelObject>(
+      VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass,
       { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 138, 138, 10);
 
-   asteroidPipeline = GraphicsPipeline_Vulkan<AsteroidVertex, SSBO_Asteroid>(physicalDevice, device, renderPass,
+   asteroidPipeline = GraphicsPipeline_Vulkan<AsteroidVertex, SSBO_Asteroid>(
+      VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass,
       { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 24, 36, 10);
 
-   laserPipeline = GraphicsPipeline_Vulkan<LaserVertex, SSBO_Laser>(physicalDevice, device, renderPass,
+   laserPipeline = GraphicsPipeline_Vulkan<LaserVertex, SSBO_Laser>(
+      VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass,
       { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 8, 18, 2);
+
+   explosionPipeline = GraphicsPipeline_Vulkan<ExplosionVertex, SSBO_Explosion>(
+      VK_PRIMITIVE_TOPOLOGY_POINT_LIST, physicalDevice, device, renderPass,
+      { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height },
+      swapChainImages, EXPLOSION_PARTICLE_COUNT, EXPLOSION_PARTICLE_COUNT, 2);
 }
 
@@ -616,4 +644,7 @@
    laser_VP_mats.view = viewMat;
    laser_VP_mats.proj = projMat;
+
+   explosion_UBO.view = viewMat;
+   explosion_UBO.proj = projMat;
 }
 
@@ -732,5 +763,5 @@
                break;
             case UI_EVENT_UNKNOWN:
-               cout << "Unknown event type: 0x" << hex << e.unknown.eventType << dec << endl;
+               //cout << "Unknown event type: 0x" << hex << e.unknown.eventType << dec << endl;
                break;
             default:
@@ -813,8 +844,18 @@
          vec3 objCenter = vec3(viewMat * vec4(asteroid.center, 1.0f));
 
-         if ((objCenter.z - asteroid.radius) > -NEAR_CLIP || asteroid.ssbo.hp <= 0.0f) {
+         if (asteroid.ssbo.hp <= 0.0f) {
             asteroid.ssbo.deleted = true;
 
-            // TODO: Create explosion here
+            // TODO: Optimize this so I don't recalculate the camera rotation every time
+            // TODO: Also, avoid re-declaring cam_pitch
+            float cam_pitch = -50.0f;
+            mat4 pitch_mat = rotate(mat4(1.0f), radians(cam_pitch), vec3(1.0f, 0.0f, 0.0f));
+            mat4 model_mat = translate(mat4(1.0f), asteroid.center) * pitch_mat;
+
+            addExplosion(model_mat, 0.5f, curTime);
+
+            // TODO: Increment player's score here
+         } else if ((objCenter.z - asteroid.radius) > -NEAR_CLIP) {
+            asteroid.ssbo.deleted = true;
          } else {
             asteroid.model_transform =
@@ -933,4 +974,12 @@
    }
 
+   for (size_t i = 0; i < explosionObjects.size(); i++) {
+      if (explosionObjects[i].modified) {
+         updateObject(explosionObjects, explosionPipeline, i);
+      }
+   }
+
+   explosion_UBO.cur_time = curTime;
+
    VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_modelPipeline[currentImage], 0, object_VP_mats);
 
@@ -940,4 +989,6 @@
 
    VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_laserPipeline[currentImage], 0, laser_VP_mats);
+
+   VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_explosionPipeline[currentImage], 0, explosion_UBO);
 }
 
@@ -1046,4 +1097,5 @@
    asteroidPipeline.cleanupBuffers();
    laserPipeline.cleanupBuffers();
+   explosionPipeline.cleanupBuffers();
 
    for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
@@ -1538,4 +1590,5 @@
       asteroidPipeline.createRenderCommands(commandBuffers[i], i);
       laserPipeline.createRenderCommands(commandBuffers[i], i);
+      explosionPipeline.createRenderCommands(commandBuffers[i], i);
 
       // Always render this pipeline last
@@ -1571,5 +1624,5 @@
 }
 
-void VulkanGame::addLaser( vec3 start, vec3 end, vec3 color, float width) {
+void VulkanGame::addLaser(vec3 start, vec3 end, vec3 color, float width) {
    vec3 ray = end - start;
    float length = glm::length(ray);
@@ -1792,4 +1845,41 @@
 }
 
+void VulkanGame::addExplosion(mat4 model_mat, float duration, float cur_time) {
+   cout << "Adding explosion..." << endl;
+
+   vector<ExplosionVertex> vertices;
+   vertices.reserve(EXPLOSION_PARTICLE_COUNT);
+
+   float particlestart_time = 0.0f;
+
+   for (int i = 0; i < EXPLOSION_PARTICLE_COUNT; i++) {
+      float randx = ((float)rand() / (float)RAND_MAX) - 0.5f;
+      float randy = ((float)rand() / (float)RAND_MAX) - 0.5f;
+
+      vertices.push_back({ vec3(randx, randy, 0.0f), particlestart_time});
+
+      particlestart_time += 0.01f;
+   }
+
+   // Fill the indices with the the first EXPLOSION_PARTICLE_COUNT ints
+   vector<uint16_t> indices(EXPLOSION_PARTICLE_COUNT);
+   iota(indices.begin(), indices.end(), 0);
+
+   SceneObject<ExplosionVertex, SSBO_Explosion>& explosion = addObject(
+      explosionObjects, explosionPipeline,
+      addObjectIndex(explosionObjects.size(), vertices),
+      indices, {
+         mat4(1.0f),
+         cur_time,
+         duration,
+         false
+      }, true);
+
+   explosion.model_base = model_mat;
+   explosion.model_transform = mat4(1.0f);
+
+   explosion.modified = true;
+}
+
 // TODO: Fix the crash that happens when alt-tabbing
 void VulkanGame::recreateSwapChain() {
@@ -1853,4 +1943,12 @@
    laserPipeline.createDescriptorPool(swapChainImages);
    laserPipeline.createDescriptorSets(swapChainImages);
+
+   createBufferSet(sizeof(UBO_Explosion), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+      uniformBuffers_explosionPipeline, uniformBuffersMemory_explosionPipeline, uniformBufferInfoList_explosionPipeline);
+
+   explosionPipeline.updateRenderPass(renderPass);
+   explosionPipeline.createPipeline("shaders/explosion-vert.spv", "shaders/explosion-frag.spv");
+   explosionPipeline.createDescriptorPool(swapChainImages);
+   explosionPipeline.createDescriptorSets(swapChainImages);
 
    createCommandBuffers();
@@ -1871,4 +1969,5 @@
    asteroidPipeline.cleanup();
    laserPipeline.cleanup();
+   explosionPipeline.cleanup();
 
    for (size_t i = 0; i < uniformBuffers_modelPipeline.size(); i++) {
@@ -1892,4 +1991,9 @@
    }
 
+   for (size_t i = 0; i < uniformBuffers_explosionPipeline.size(); i++) {
+      vkDestroyBuffer(device, uniformBuffers_explosionPipeline[i], nullptr);
+      vkFreeMemory(device, uniformBuffersMemory_explosionPipeline[i], nullptr);
+   }
+
    vkDestroyRenderPass(device, renderPass, nullptr);
 
Index: vulkan-game.hpp
===================================================================
--- vulkan-game.hpp	(revision 73a10caea6c4ac9731862d7f1ed503afd065643b)
+++ vulkan-game.hpp	(revision 845a2cb7a34c5349a49ed51c2b9111b3eba65572)
@@ -57,7 +57,8 @@
 };
 
-struct UBO_VP_mats {
-   alignas(16) mat4 view;
-   alignas(16) mat4 proj;
+struct ExplosionVertex {
+   vec3 particleStartVelocity;
+   float particleStartTime;
+   unsigned int objIndex;
 };
 
@@ -76,4 +77,22 @@
    alignas(4) vec3 color;
    alignas(4) unsigned int deleted;
+};
+
+struct SSBO_Explosion {
+   alignas(16) mat4 model;
+   alignas(4) float explosionStartTime;
+   alignas(4) float explosionDuration;
+   alignas(4) unsigned int deleted;
+};
+
+struct UBO_VP_mats {
+   alignas(16) mat4 view;
+   alignas(16) mat4 proj;
+};
+
+struct UBO_Explosion {
+   alignas(16) mat4 view;
+   alignas(16) mat4 proj;
+   alignas(4) float cur_time;
 };
 
@@ -175,4 +194,6 @@
 
    private:
+      // TODO: Make these consts static
+
       const int MAX_FRAMES_IN_FLIGHT;
 
@@ -180,4 +201,6 @@
       const float FAR_CLIP = 100.0f;
       const float FOV_ANGLE = 67.0f; // means the camera lens goes from -33 deg to 33 def
+
+      const int EXPLOSION_PARTICLE_COUNT = 300;
 
       vec3 cam_pos;
@@ -285,4 +308,13 @@
 
       UBO_VP_mats laser_VP_mats;
+
+      GraphicsPipeline_Vulkan<ExplosionVertex, SSBO_Explosion> explosionPipeline;
+      vector<SceneObject<ExplosionVertex, SSBO_Explosion>> explosionObjects;
+
+      vector<VkBuffer> uniformBuffers_explosionPipeline;
+      vector<VkDeviceMemory> uniformBuffersMemory_explosionPipeline;
+      vector<VkDescriptorBufferInfo> uniformBufferInfoList_explosionPipeline;
+
+      UBO_Explosion explosion_UBO;
 
       vector<BaseEffectOverTime*> effects;
@@ -334,10 +366,4 @@
       void createSyncObjects();
 
-      void addLaser(vec3 start, vec3 end, vec3 color, float width);
-      void translateLaser(size_t index, const vec3& translation);
-      void updateLaserTarget(size_t index);
-      bool getLaserAndAsteroidIntersection(SceneObject<AsteroidVertex, SSBO_Asteroid>& asteroid,
-            vec3& start, vec3& end, vec3& intersection);
-
       // TODO: Since addObject() returns a reference to the new object now,
       // stop using objects.back() to access the object that was just created
@@ -366,4 +392,12 @@
       void centerObject(SceneObject<VertexType, SSBOType>& object);
 
+      void addLaser(vec3 start, vec3 end, vec3 color, float width);
+      void translateLaser(size_t index, const vec3& translation);
+      void updateLaserTarget(size_t index);
+      bool getLaserAndAsteroidIntersection(SceneObject<AsteroidVertex, SSBO_Asteroid>& asteroid,
+            vec3& start, vec3& end, vec3& intersection);
+
+      void addExplosion(mat4 model_mat, float duration, float cur_time);
+
       void createBufferSet(VkDeviceSize bufferSize, VkBufferUsageFlags flags,
             vector<VkBuffer>& buffers, vector<VkDeviceMemory>& buffersMemory,
@@ -380,4 +414,12 @@
             void* pUserData);
 };
+
+// Start of specialized no-op functions
+
+template<>
+inline void VulkanGame::centerObject(SceneObject<ExplosionVertex, SSBO_Explosion>& object) {
+}
+
+// End of specialized no-op functions
 
 // TODO: Right now, it's basically necessary to pass the identity matrix in for ssbo.model
@@ -407,5 +449,5 @@
    SceneObject<VertexType, SSBOType>& obj = objects.back();
 
-   if (!is_same_v<VertexType, LaserVertex>) {
+   if (!is_same_v<VertexType, LaserVertex> && !is_same_v<VertexType, ExplosionVertex>) {
       centerObject(obj);
    }
