diff --git a/src/Entity.h b/src/Entity.h index f94af49..b570ea3 100644 --- a/src/Entity.h +++ b/src/Entity.h @@ -1,7 +1,10 @@ #pragma once #include "Transform.h" +#include "VulkanAcceleration.h" #include #include +#include +#include enum class EntityType { STATIC, // Objects that never move (buildings, ground, barriers) @@ -23,15 +26,74 @@ class Entity { // State bool isActive = true; + bool isDeformed = false; - // Physics (for future use) + // Physics glm::vec3 velocity = glm::vec3(0.0f); glm::vec3 angularVelocity = glm::vec3(0.0f); + float mass = 1500.0f; // kg (typical car) + float elasticity = 0.3f; // 0.0 (plastic) to 1.0 (perfectly elastic) + float structuralIntegrity = 1.0f; // 1.0 (new) to 0.0 (destroyed) // Getters bool isDynamic() const { return type == EntityType::DYNAMIC; } bool isStatic() const { return type == EntityType::STATIC; } + // Apply impact and deform mesh + void applyImpact(glm::vec3 worldImpactPoint, glm::vec3 impulse, GeometryObject& geom) { + if (!isDynamic() || !isActive) return; + + // Transform world impact point to local space + glm::mat4 modelMatrix = transform.getMatrix(); + glm::mat4 invModelMatrix = glm::inverse(modelMatrix); + glm::vec3 localImpactPoint = glm::vec3(invModelMatrix * glm::vec4(worldImpactPoint, 1.0f)); + glm::vec3 localImpulse = glm::vec3(invModelMatrix * glm::vec4(impulse, 0.0f)); + + float impulseMagnitude = glm::length(localImpulse); + if (impulseMagnitude < 0.1f) return; + + // Damage calculation + float damage = impulseMagnitude / (mass * 10.0f); + structuralIntegrity = std::max(0.0f, structuralIntegrity - damage); + + // If structural integrity is zero, deactivate the entity (total wreck) + if (structuralIntegrity <= 0.001f) { + isActive = false; + return; + } + + // Deformation radius depends on mass and impulse + float radius = 0.5f + (impulseMagnitude / mass) * 2.0f; + + // Deform vertices + bool changed = false; + for (auto& vertex : geom.cpuVertices) { + float dist = glm::distance(vertex.pos, localImpactPoint); + if (dist < radius) { + // Falloff based on distance + float falloff = 1.0f - (dist / radius); + + // Displacement + glm::vec3 displacement = localImpulse * falloff * 0.001f; // Scale down for mesh units + + // Breaking/Piercing: if impulse is very high, push vertex "away" (simulating a hole) + if (impulseMagnitude > mass * 50.0f && falloff > 0.7f) { + vertex.pos += glm::normalize(localImpulse) * 10.0f; // Push far away + } else { + vertex.pos += displacement; + // Skew normal slightly to reflect deformation + vertex.normal = glm::normalize(vertex.normal + displacement * 0.5f); + } + + changed = true; + } + } + + if (changed) { + isDeformed = true; + } + } + // Update (called each frame for dynamic objects) void update(float deltaTime) { if (!isDynamic() || !isActive) return; @@ -45,5 +107,9 @@ class Entity { glm::vec3 axis = glm::normalize(angularVelocity); transform.rotate(angle, axis); } + + // Basic air resistance/friction + velocity *= (1.0f - 0.1f * deltaTime); + angularVelocity *= (1.0f - 0.2f * deltaTime); } }; diff --git a/src/VulkanAcceleration.cpp b/src/VulkanAcceleration.cpp index 8821e82..8cca93a 100644 --- a/src/VulkanAcceleration.cpp +++ b/src/VulkanAcceleration.cpp @@ -59,6 +59,8 @@ void VulkanAcceleration::createGroundPlane(VkPhysicalDevice physicalDevice, VkDe GeometryObject obj; obj.vertexCount = static_cast(vertices.size()); obj.indexCount = static_cast(indices.size()); + obj.cpuVertices = vertices; + obj.cpuIndices = indices; VkDeviceSize vertexBufferSize = sizeof(Vertex) * vertices.size(); VkDeviceSize indexBufferSize = sizeof(uint32_t) * indices.size(); @@ -205,6 +207,8 @@ void VulkanAcceleration::createCube(VkPhysicalDevice physicalDevice, VkDevice de GeometryObject obj; obj.vertexCount = static_cast(vertices.size()); obj.indexCount = static_cast(indices.size()); + obj.cpuVertices = vertices; + obj.cpuIndices = indices; VkDeviceSize vertexBufferSize = sizeof(Vertex) * vertices.size(); VkDeviceSize indexBufferSize = sizeof(uint32_t) * indices.size(); @@ -316,7 +320,8 @@ void VulkanAcceleration::createBottomLevelAS(VkPhysicalDevice physicalDevice, Vk // EXCLUSIVE OPTIMIZATION: PREFER_FAST_TRACE // We sacrifice build time to ensure maximum traversal speed on RT Cores. // This fits the "Exclusive Processing" requirement. - buildInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR; + buildInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR | + VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR; buildInfo.geometryCount = 1; buildInfo.pGeometries = &asGeometry; @@ -771,6 +776,144 @@ void VulkanAcceleration::recordTLASUpdate(VkCommandBuffer commandBuffer) { 0, 1, &memBarrierAfter, 0, nullptr, 0, nullptr); } +void VulkanAcceleration::updateGeometry(VkPhysicalDevice physicalDevice, VkDevice device, + VkQueue graphicsQueue, VkCommandPool commandPool, + uint32_t objectIndex) { + if (objectIndex >= objects.size()) return; + + GeometryObject& obj = objects[objectIndex]; + VkDeviceSize vertexBufferSize = sizeof(Vertex) * obj.cpuVertices.size(); + + // Create staging buffer + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(physicalDevice, device, vertexBufferSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, vertexBufferSize, 0, &data); + memcpy(data, obj.cpuVertices.data(), vertexBufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + // Copy to vertex buffer + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + VkBufferCopy copyRegion{}; + copyRegion.size = vertexBufferSize; + vkCmdCopyBuffer(commandBuffer, stagingBuffer, obj.vertexBuffer, 1, ©Region); + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + + // Update BLAS + updateBottomLevelAS(physicalDevice, device, graphicsQueue, commandPool, obj); +} + +void VulkanAcceleration::updateBottomLevelAS(VkPhysicalDevice physicalDevice, VkDevice device, + VkQueue graphicsQueue, VkCommandPool commandPool, + GeometryObject& object) { + VkAccelerationStructureGeometryKHR asGeometry{}; + asGeometry.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR; + asGeometry.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR; + asGeometry.flags = VK_GEOMETRY_OPAQUE_BIT_KHR; + asGeometry.geometry.triangles.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR; + asGeometry.geometry.triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; + asGeometry.geometry.triangles.vertexData.deviceAddress = getBufferDeviceAddress(device, object.vertexBuffer); + asGeometry.geometry.triangles.vertexStride = sizeof(Vertex); + asGeometry.geometry.triangles.maxVertex = object.vertexCount; + asGeometry.geometry.triangles.indexType = VK_INDEX_TYPE_UINT32; + asGeometry.geometry.triangles.indexData.deviceAddress = getBufferDeviceAddress(device, object.indexBuffer); + + VkAccelerationStructureBuildGeometryInfoKHR buildInfo{}; + buildInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR; + buildInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR; + buildInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR | + VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR; + buildInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_UPDATE_KHR; + buildInfo.srcAccelerationStructure = object.blas.handle; + buildInfo.dstAccelerationStructure = object.blas.handle; + buildInfo.geometryCount = 1; + buildInfo.pGeometries = &asGeometry; + + uint32_t primitiveCount = object.indexCount / 3; + + VkAccelerationStructureBuildSizesInfoKHR sizeInfo{}; + sizeInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR; + vkGetAccelerationStructureBuildSizesKHR(device, VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, + &buildInfo, &primitiveCount, &sizeInfo); + + // Create scratch buffer for update + VkBuffer scratchBuffer; + VkDeviceMemory scratchMemory; + createBuffer(physicalDevice, device, sizeInfo.updateScratchSize, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + scratchBuffer, scratchMemory); + + buildInfo.scratchData.deviceAddress = getBufferDeviceAddress(device, scratchBuffer); + + VkAccelerationStructureBuildRangeInfoKHR rangeInfo{}; + rangeInfo.primitiveCount = primitiveCount; + rangeInfo.primitiveOffset = 0; + rangeInfo.firstVertex = 0; + rangeInfo.transformOffset = 0; + + const VkAccelerationStructureBuildRangeInfoKHR* pRangeInfo = &rangeInfo; + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + vkCmdBuildAccelerationStructuresKHR(commandBuffer, 1, &buildInfo, &pRangeInfo); + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + vkDestroyBuffer(device, scratchBuffer, nullptr); + vkFreeMemory(device, scratchMemory, nullptr); +} + void VulkanAcceleration::cleanup(VkDevice device) { // Cleanup all objects for (auto& obj : objects) { diff --git a/src/VulkanAcceleration.h b/src/VulkanAcceleration.h index 853b9f4..16d761e 100644 --- a/src/VulkanAcceleration.h +++ b/src/VulkanAcceleration.h @@ -27,6 +27,10 @@ struct GeometryObject { uint32_t vertexCount = 0; uint32_t indexCount = 0; AccelerationStructure blas; + + // CPU copies for deformation/destruction + std::vector cpuVertices; + std::vector cpuIndices; }; class VulkanAcceleration { @@ -74,6 +78,16 @@ class VulkanAcceleration { VkQueue graphicsQueue, VkCommandPool commandPool, GeometryObject& object); + // Refit/Update BLAS after geometry modification + void updateBottomLevelAS(VkPhysicalDevice physicalDevice, VkDevice device, + VkQueue graphicsQueue, VkCommandPool commandPool, + GeometryObject& object); + + // Helper to upload modified vertices to GPU + void updateGeometry(VkPhysicalDevice physicalDevice, VkDevice device, + VkQueue graphicsQueue, VkCommandPool commandPool, + uint32_t objectIndex); + void createTopLevelAS(VkPhysicalDevice physicalDevice, VkDevice device, VkQueue graphicsQueue, VkCommandPool commandPool); diff --git a/src/main.cpp b/src/main.cpp index 25c0914..4eebb1f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -187,6 +187,77 @@ class RacingEngine { // Async Logger AsyncLogger logger; + // Collision Detection and Response + void resolveCollisions() { + auto& entities = entityManager.getEntities(); + + for (size_t i = 0; i < entities.size(); i++) { + auto& a = entities[i]; + if (!a->isActive) continue; + + // Collision with ground plane (Y=0) + float halfScaleY = a->transform.scale.y * 0.5f; + if (a->transform.position.y - halfScaleY < 0.0f) { + // Impact point on ground + glm::vec3 impactPoint = a->transform.position; + impactPoint.y = 0.0f; + + // Impulse calculation (simplistic) + glm::vec3 impulse = -a->velocity * a->mass * (1.0f + a->elasticity); + impulse.x *= 0.1f; impulse.z *= 0.1f; // Friction + + a->applyImpact(impactPoint, impulse, acceleration.objects[a->geometryIndex]); + + // Response + a->transform.position.y = halfScaleY; + a->velocity.y *= -a->elasticity; + a->velocity.x *= 0.9f; a->velocity.z *= 0.9f; // Ground friction + } + + // Collision with other entities + for (size_t j = i + 1; j < entities.size(); j++) { + auto& b = entities[j]; + if (!b->isActive) continue; + + // Simple AABB-AABB check + glm::vec3 aMin = a->transform.position - a->transform.scale * 0.5f; + glm::vec3 aMax = a->transform.position + a->transform.scale * 0.5f; + glm::vec3 bMin = b->transform.position - b->transform.scale * 0.5f; + glm::vec3 bMax = b->transform.position + b->transform.scale * 0.5f; + + bool collision = (aMin.x <= bMax.x && aMax.x >= bMin.x) && + (aMin.y <= bMax.y && aMax.y >= bMin.y) && + (aMin.z <= bMax.z && aMax.z >= bMin.z); + + if (collision) { + // Collision point (simplistic: midpoint of overlap) + glm::vec3 impactPoint = (glm::max(aMin, bMin) + glm::min(aMax, bMax)) * 0.5f; + + // Relative velocity + glm::vec3 relVel = a->velocity - b->velocity; + + // Impulse magnitude + float invMassSum = (a->isDynamic() ? 1.0f/a->mass : 0.0f) + + (b->isDynamic() ? 1.0f/b->mass : 0.0f); + + if (invMassSum > 0.0f) { + float j_mag = -(1.0f + (a->elasticity + b->elasticity)*0.5f) * glm::dot(relVel, glm::normalize(a->transform.position - b->transform.position)) / invMassSum; + glm::vec3 impulse = glm::normalize(a->transform.position - b->transform.position) * std::max(0.0f, j_mag); + + if (a->isDynamic()) { + a->applyImpact(impactPoint, impulse, acceleration.objects[a->geometryIndex]); + a->velocity += impulse / a->mass; + } + if (b->isDynamic()) { + b->applyImpact(impactPoint, -impulse, acceleration.objects[b->geometryIndex]); + b->velocity -= impulse / b->mass; + } + } + } + } + } + } + // UX State Tracking void updateWindowTitle() { glm::vec3 pos = camera.getPosition(); @@ -864,6 +935,19 @@ class RacingEngine { // Update entities entityManager.update(deltaTime); + // Resolve collisions and apply deformations + resolveCollisions(); + + // Update Vulkan geometry for deformed objects + for (size_t i = 0; i < entityManager.getEntities().size(); i++) { + auto& entity = entityManager.getEntities()[i]; + if (entity->isDeformed) { + acceleration.updateGeometry(physicalDevice, device, graphicsQueue, rayTracing.commandPool, entity->geometryIndex); + entity->isDeformed = false; + accumulationFrames = 0; // Reset accumulation when geometry changes + } + } + // Prepare TLAS instance data (CPU-side, before command buffer recording) if (entityManager.countDynamic() > 0) { const auto& transforms = entityManager.getTransforms(); diff --git a/tests/test_deformation.cpp b/tests/test_deformation.cpp new file mode 100644 index 0000000..e5a037f --- /dev/null +++ b/tests/test_deformation.cpp @@ -0,0 +1,107 @@ +#include +#include +#include +#include +#include +#include + +// Mock Vulkan and local structures for testing +struct Vertex { + glm::vec3 pos; + glm::vec3 normal; +}; + +struct AccelerationStructure { + int handle = 0; +}; + +struct GeometryObject { + std::vector cpuVertices; + std::vector cpuIndices; +}; + +struct Transform { + glm::vec3 position = glm::vec3(0.0f); + glm::vec3 scale = glm::vec3(1.0f); + glm::quat rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); + + glm::mat4 getMatrix() const { + glm::mat4 model = glm::mat4(1.0f); + model = glm::translate(model, position); + model = model * glm::mat4_cast(rotation); + model = glm::scale(model, scale); + return model; + } +}; + +enum class EntityType { STATIC, DYNAMIC }; + +class Entity { +public: + Transform transform; + bool isActive = true; + bool isDeformed = false; + EntityType type = EntityType::DYNAMIC; + float mass = 1500.0f; + float structuralIntegrity = 1.0f; + + bool isDynamic() const { return type == EntityType::DYNAMIC; } + + void applyImpact(glm::vec3 worldImpactPoint, glm::vec3 impulse, GeometryObject& geom) { + if (!isDynamic() || !isActive) return; + + glm::mat4 modelMatrix = transform.getMatrix(); + glm::mat4 invModelMatrix = glm::inverse(modelMatrix); + glm::vec3 localImpactPoint = glm::vec3(invModelMatrix * glm::vec4(worldImpactPoint, 1.0f)); + glm::vec3 localImpulse = glm::vec3(invModelMatrix * glm::vec4(impulse, 0.0f)); + + float impulseMagnitude = glm::length(localImpulse); + if (impulseMagnitude < 0.1f) return; + + float damage = impulseMagnitude / (mass * 10.0f); + structuralIntegrity = std::max(0.0f, structuralIntegrity - damage); + + float radius = 0.5f + (impulseMagnitude / mass) * 2.0f; + + bool changed = false; + for (auto& vertex : geom.cpuVertices) { + float dist = glm::distance(vertex.pos, localImpactPoint); + if (dist < radius) { + float falloff = 1.0f - (dist / radius); + glm::vec3 displacement = localImpulse * falloff * 0.001f; + + if (impulseMagnitude > mass * 50.0f && falloff > 0.7f) { + vertex.pos += glm::normalize(localImpulse) * 10.0f; + } else { + vertex.pos += displacement; + } + changed = true; + } + } + if (changed) isDeformed = true; + } +}; + +int main() { + Entity car; + car.transform.position = glm::vec3(10.0f, 0.0f, 0.0f); + car.transform.scale = glm::vec3(2.0f); + + GeometryObject geom; + geom.cpuVertices.push_back({{0.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}}); // Center + geom.cpuVertices.push_back({{0.5f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}}); // Near center + + std::cout << "Original Vertex 0: (" << geom.cpuVertices[0].pos.x << ", " << geom.cpuVertices[0].pos.y << ", " << geom.cpuVertices[0].pos.z << ")\n"; + + // Impact at car center in world space + glm::vec3 impactPoint = glm::vec3(10.0f, 0.0f, 0.0f); + glm::vec3 impulse = glm::vec3(-100000.0f, 0.0f, 0.0f); // Strong impulse + + car.applyImpact(impactPoint, impulse, geom); + + std::cout << "Deformed Vertex 0: (" << geom.cpuVertices[0].pos.x << ", " << geom.cpuVertices[0].pos.y << ", " << geom.cpuVertices[0].pos.z << ")\n"; + std::cout << "Structural Integrity: " << car.structuralIntegrity << "\n"; + std::cout << "Is Deformed: " << (car.isDeformed ? "Yes" : "No") << "\n"; + + return 0; +}