Skip to content

Commit d624843

Browse files
authored
Merge pull request #112 from TECHNICANGEL/feature/car-deformation-destruction-4299456471065240307
Physical Car Deformation and Destruction System
2 parents f8e59f9 + d0c4d0b commit d624843

File tree

5 files changed

+416
-2
lines changed

5 files changed

+416
-2
lines changed

src/Entity.h

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
#pragma once
22
#include "Transform.h"
3+
#include "VulkanAcceleration.h"
34
#include <string>
45
#include <cstdint>
6+
#include <glm/gtc/matrix_inverse.hpp>
7+
#include <algorithm>
58

69
enum class EntityType {
710
STATIC, // Objects that never move (buildings, ground, barriers)
@@ -23,15 +26,74 @@ class Entity {
2326

2427
// State
2528
bool isActive = true;
29+
bool isDeformed = false;
2630

27-
// Physics (for future use)
31+
// Physics
2832
glm::vec3 velocity = glm::vec3(0.0f);
2933
glm::vec3 angularVelocity = glm::vec3(0.0f);
34+
float mass = 1500.0f; // kg (typical car)
35+
float elasticity = 0.3f; // 0.0 (plastic) to 1.0 (perfectly elastic)
36+
float structuralIntegrity = 1.0f; // 1.0 (new) to 0.0 (destroyed)
3037

3138
// Getters
3239
bool isDynamic() const { return type == EntityType::DYNAMIC; }
3340
bool isStatic() const { return type == EntityType::STATIC; }
3441

42+
// Apply impact and deform mesh
43+
void applyImpact(glm::vec3 worldImpactPoint, glm::vec3 impulse, GeometryObject& geom) {
44+
if (!isDynamic() || !isActive) return;
45+
46+
// Transform world impact point to local space
47+
glm::mat4 modelMatrix = transform.getMatrix();
48+
glm::mat4 invModelMatrix = glm::inverse(modelMatrix);
49+
glm::vec3 localImpactPoint = glm::vec3(invModelMatrix * glm::vec4(worldImpactPoint, 1.0f));
50+
glm::vec3 localImpulse = glm::vec3(invModelMatrix * glm::vec4(impulse, 0.0f));
51+
52+
float impulseMagnitude = glm::length(localImpulse);
53+
if (impulseMagnitude < 0.1f) return;
54+
55+
// Damage calculation
56+
float damage = impulseMagnitude / (mass * 10.0f);
57+
structuralIntegrity = std::max(0.0f, structuralIntegrity - damage);
58+
59+
// If structural integrity is zero, deactivate the entity (total wreck)
60+
if (structuralIntegrity <= 0.001f) {
61+
isActive = false;
62+
return;
63+
}
64+
65+
// Deformation radius depends on mass and impulse
66+
float radius = 0.5f + (impulseMagnitude / mass) * 2.0f;
67+
68+
// Deform vertices
69+
bool changed = false;
70+
for (auto& vertex : geom.cpuVertices) {
71+
float dist = glm::distance(vertex.pos, localImpactPoint);
72+
if (dist < radius) {
73+
// Falloff based on distance
74+
float falloff = 1.0f - (dist / radius);
75+
76+
// Displacement
77+
glm::vec3 displacement = localImpulse * falloff * 0.001f; // Scale down for mesh units
78+
79+
// Breaking/Piercing: if impulse is very high, push vertex "away" (simulating a hole)
80+
if (impulseMagnitude > mass * 50.0f && falloff > 0.7f) {
81+
vertex.pos += glm::normalize(localImpulse) * 10.0f; // Push far away
82+
} else {
83+
vertex.pos += displacement;
84+
// Skew normal slightly to reflect deformation
85+
vertex.normal = glm::normalize(vertex.normal + displacement * 0.5f);
86+
}
87+
88+
changed = true;
89+
}
90+
}
91+
92+
if (changed) {
93+
isDeformed = true;
94+
}
95+
}
96+
3597
// Update (called each frame for dynamic objects)
3698
void update(float deltaTime) {
3799
if (!isDynamic() || !isActive) return;
@@ -45,5 +107,9 @@ class Entity {
45107
glm::vec3 axis = glm::normalize(angularVelocity);
46108
transform.rotate(angle, axis);
47109
}
110+
111+
// Basic air resistance/friction
112+
velocity *= (1.0f - 0.1f * deltaTime);
113+
angularVelocity *= (1.0f - 0.2f * deltaTime);
48114
}
49115
};

src/VulkanAcceleration.cpp

Lines changed: 144 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ void VulkanAcceleration::createGroundPlane(VkPhysicalDevice physicalDevice, VkDe
5959
GeometryObject obj;
6060
obj.vertexCount = static_cast<uint32_t>(vertices.size());
6161
obj.indexCount = static_cast<uint32_t>(indices.size());
62+
obj.cpuVertices = vertices;
63+
obj.cpuIndices = indices;
6264

6365
VkDeviceSize vertexBufferSize = sizeof(Vertex) * vertices.size();
6466
VkDeviceSize indexBufferSize = sizeof(uint32_t) * indices.size();
@@ -205,6 +207,8 @@ void VulkanAcceleration::createCube(VkPhysicalDevice physicalDevice, VkDevice de
205207
GeometryObject obj;
206208
obj.vertexCount = static_cast<uint32_t>(vertices.size());
207209
obj.indexCount = static_cast<uint32_t>(indices.size());
210+
obj.cpuVertices = vertices;
211+
obj.cpuIndices = indices;
208212

209213
VkDeviceSize vertexBufferSize = sizeof(Vertex) * vertices.size();
210214
VkDeviceSize indexBufferSize = sizeof(uint32_t) * indices.size();
@@ -316,7 +320,8 @@ void VulkanAcceleration::createBottomLevelAS(VkPhysicalDevice physicalDevice, Vk
316320
// EXCLUSIVE OPTIMIZATION: PREFER_FAST_TRACE
317321
// We sacrifice build time to ensure maximum traversal speed on RT Cores.
318322
// This fits the "Exclusive Processing" requirement.
319-
buildInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
323+
buildInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR |
324+
VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR;
320325
buildInfo.geometryCount = 1;
321326
buildInfo.pGeometries = &asGeometry;
322327

@@ -771,6 +776,144 @@ void VulkanAcceleration::recordTLASUpdate(VkCommandBuffer commandBuffer) {
771776
0, 1, &memBarrierAfter, 0, nullptr, 0, nullptr);
772777
}
773778

779+
void VulkanAcceleration::updateGeometry(VkPhysicalDevice physicalDevice, VkDevice device,
780+
VkQueue graphicsQueue, VkCommandPool commandPool,
781+
uint32_t objectIndex) {
782+
if (objectIndex >= objects.size()) return;
783+
784+
GeometryObject& obj = objects[objectIndex];
785+
VkDeviceSize vertexBufferSize = sizeof(Vertex) * obj.cpuVertices.size();
786+
787+
// Create staging buffer
788+
VkBuffer stagingBuffer;
789+
VkDeviceMemory stagingBufferMemory;
790+
createBuffer(physicalDevice, device, vertexBufferSize,
791+
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
792+
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
793+
stagingBuffer, stagingBufferMemory);
794+
795+
void* data;
796+
vkMapMemory(device, stagingBufferMemory, 0, vertexBufferSize, 0, &data);
797+
memcpy(data, obj.cpuVertices.data(), vertexBufferSize);
798+
vkUnmapMemory(device, stagingBufferMemory);
799+
800+
// Copy to vertex buffer
801+
VkCommandBufferAllocateInfo allocInfo{};
802+
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
803+
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
804+
allocInfo.commandPool = commandPool;
805+
allocInfo.commandBufferCount = 1;
806+
807+
VkCommandBuffer commandBuffer;
808+
vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer);
809+
810+
VkCommandBufferBeginInfo beginInfo{};
811+
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
812+
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
813+
814+
vkBeginCommandBuffer(commandBuffer, &beginInfo);
815+
VkBufferCopy copyRegion{};
816+
copyRegion.size = vertexBufferSize;
817+
vkCmdCopyBuffer(commandBuffer, stagingBuffer, obj.vertexBuffer, 1, &copyRegion);
818+
vkEndCommandBuffer(commandBuffer);
819+
820+
VkSubmitInfo submitInfo{};
821+
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
822+
submitInfo.commandBufferCount = 1;
823+
submitInfo.pCommandBuffers = &commandBuffer;
824+
825+
vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
826+
vkQueueWaitIdle(graphicsQueue);
827+
828+
vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
829+
vkDestroyBuffer(device, stagingBuffer, nullptr);
830+
vkFreeMemory(device, stagingBufferMemory, nullptr);
831+
832+
// Update BLAS
833+
updateBottomLevelAS(physicalDevice, device, graphicsQueue, commandPool, obj);
834+
}
835+
836+
void VulkanAcceleration::updateBottomLevelAS(VkPhysicalDevice physicalDevice, VkDevice device,
837+
VkQueue graphicsQueue, VkCommandPool commandPool,
838+
GeometryObject& object) {
839+
VkAccelerationStructureGeometryKHR asGeometry{};
840+
asGeometry.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR;
841+
asGeometry.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR;
842+
asGeometry.flags = VK_GEOMETRY_OPAQUE_BIT_KHR;
843+
asGeometry.geometry.triangles.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR;
844+
asGeometry.geometry.triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT;
845+
asGeometry.geometry.triangles.vertexData.deviceAddress = getBufferDeviceAddress(device, object.vertexBuffer);
846+
asGeometry.geometry.triangles.vertexStride = sizeof(Vertex);
847+
asGeometry.geometry.triangles.maxVertex = object.vertexCount;
848+
asGeometry.geometry.triangles.indexType = VK_INDEX_TYPE_UINT32;
849+
asGeometry.geometry.triangles.indexData.deviceAddress = getBufferDeviceAddress(device, object.indexBuffer);
850+
851+
VkAccelerationStructureBuildGeometryInfoKHR buildInfo{};
852+
buildInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR;
853+
buildInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
854+
buildInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR |
855+
VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR;
856+
buildInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_UPDATE_KHR;
857+
buildInfo.srcAccelerationStructure = object.blas.handle;
858+
buildInfo.dstAccelerationStructure = object.blas.handle;
859+
buildInfo.geometryCount = 1;
860+
buildInfo.pGeometries = &asGeometry;
861+
862+
uint32_t primitiveCount = object.indexCount / 3;
863+
864+
VkAccelerationStructureBuildSizesInfoKHR sizeInfo{};
865+
sizeInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR;
866+
vkGetAccelerationStructureBuildSizesKHR(device, VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR,
867+
&buildInfo, &primitiveCount, &sizeInfo);
868+
869+
// Create scratch buffer for update
870+
VkBuffer scratchBuffer;
871+
VkDeviceMemory scratchMemory;
872+
createBuffer(physicalDevice, device, sizeInfo.updateScratchSize,
873+
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
874+
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
875+
scratchBuffer, scratchMemory);
876+
877+
buildInfo.scratchData.deviceAddress = getBufferDeviceAddress(device, scratchBuffer);
878+
879+
VkAccelerationStructureBuildRangeInfoKHR rangeInfo{};
880+
rangeInfo.primitiveCount = primitiveCount;
881+
rangeInfo.primitiveOffset = 0;
882+
rangeInfo.firstVertex = 0;
883+
rangeInfo.transformOffset = 0;
884+
885+
const VkAccelerationStructureBuildRangeInfoKHR* pRangeInfo = &rangeInfo;
886+
887+
VkCommandBufferAllocateInfo allocInfo{};
888+
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
889+
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
890+
allocInfo.commandPool = commandPool;
891+
allocInfo.commandBufferCount = 1;
892+
893+
VkCommandBuffer commandBuffer;
894+
vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer);
895+
896+
VkCommandBufferBeginInfo beginInfo{};
897+
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
898+
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
899+
900+
vkBeginCommandBuffer(commandBuffer, &beginInfo);
901+
vkCmdBuildAccelerationStructuresKHR(commandBuffer, 1, &buildInfo, &pRangeInfo);
902+
vkEndCommandBuffer(commandBuffer);
903+
904+
VkSubmitInfo submitInfo{};
905+
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
906+
submitInfo.commandBufferCount = 1;
907+
submitInfo.pCommandBuffers = &commandBuffer;
908+
909+
vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
910+
vkQueueWaitIdle(graphicsQueue);
911+
912+
vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
913+
vkDestroyBuffer(device, scratchBuffer, nullptr);
914+
vkFreeMemory(device, scratchMemory, nullptr);
915+
}
916+
774917
void VulkanAcceleration::cleanup(VkDevice device) {
775918
// Cleanup all objects
776919
for (auto& obj : objects) {

src/VulkanAcceleration.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ struct GeometryObject {
2727
uint32_t vertexCount = 0;
2828
uint32_t indexCount = 0;
2929
AccelerationStructure blas;
30+
31+
// CPU copies for deformation/destruction
32+
std::vector<Vertex> cpuVertices;
33+
std::vector<uint32_t> cpuIndices;
3034
};
3135

3236
class VulkanAcceleration {
@@ -74,6 +78,16 @@ class VulkanAcceleration {
7478
VkQueue graphicsQueue, VkCommandPool commandPool,
7579
GeometryObject& object);
7680

81+
// Refit/Update BLAS after geometry modification
82+
void updateBottomLevelAS(VkPhysicalDevice physicalDevice, VkDevice device,
83+
VkQueue graphicsQueue, VkCommandPool commandPool,
84+
GeometryObject& object);
85+
86+
// Helper to upload modified vertices to GPU
87+
void updateGeometry(VkPhysicalDevice physicalDevice, VkDevice device,
88+
VkQueue graphicsQueue, VkCommandPool commandPool,
89+
uint32_t objectIndex);
90+
7791
void createTopLevelAS(VkPhysicalDevice physicalDevice, VkDevice device,
7892
VkQueue graphicsQueue, VkCommandPool commandPool);
7993

src/main.cpp

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,77 @@ class RacingEngine {
187187
// Async Logger
188188
AsyncLogger logger;
189189

190+
// Collision Detection and Response
191+
void resolveCollisions() {
192+
auto& entities = entityManager.getEntities();
193+
194+
for (size_t i = 0; i < entities.size(); i++) {
195+
auto& a = entities[i];
196+
if (!a->isActive) continue;
197+
198+
// Collision with ground plane (Y=0)
199+
float halfScaleY = a->transform.scale.y * 0.5f;
200+
if (a->transform.position.y - halfScaleY < 0.0f) {
201+
// Impact point on ground
202+
glm::vec3 impactPoint = a->transform.position;
203+
impactPoint.y = 0.0f;
204+
205+
// Impulse calculation (simplistic)
206+
glm::vec3 impulse = -a->velocity * a->mass * (1.0f + a->elasticity);
207+
impulse.x *= 0.1f; impulse.z *= 0.1f; // Friction
208+
209+
a->applyImpact(impactPoint, impulse, acceleration.objects[a->geometryIndex]);
210+
211+
// Response
212+
a->transform.position.y = halfScaleY;
213+
a->velocity.y *= -a->elasticity;
214+
a->velocity.x *= 0.9f; a->velocity.z *= 0.9f; // Ground friction
215+
}
216+
217+
// Collision with other entities
218+
for (size_t j = i + 1; j < entities.size(); j++) {
219+
auto& b = entities[j];
220+
if (!b->isActive) continue;
221+
222+
// Simple AABB-AABB check
223+
glm::vec3 aMin = a->transform.position - a->transform.scale * 0.5f;
224+
glm::vec3 aMax = a->transform.position + a->transform.scale * 0.5f;
225+
glm::vec3 bMin = b->transform.position - b->transform.scale * 0.5f;
226+
glm::vec3 bMax = b->transform.position + b->transform.scale * 0.5f;
227+
228+
bool collision = (aMin.x <= bMax.x && aMax.x >= bMin.x) &&
229+
(aMin.y <= bMax.y && aMax.y >= bMin.y) &&
230+
(aMin.z <= bMax.z && aMax.z >= bMin.z);
231+
232+
if (collision) {
233+
// Collision point (simplistic: midpoint of overlap)
234+
glm::vec3 impactPoint = (glm::max(aMin, bMin) + glm::min(aMax, bMax)) * 0.5f;
235+
236+
// Relative velocity
237+
glm::vec3 relVel = a->velocity - b->velocity;
238+
239+
// Impulse magnitude
240+
float invMassSum = (a->isDynamic() ? 1.0f/a->mass : 0.0f) +
241+
(b->isDynamic() ? 1.0f/b->mass : 0.0f);
242+
243+
if (invMassSum > 0.0f) {
244+
float j_mag = -(1.0f + (a->elasticity + b->elasticity)*0.5f) * glm::dot(relVel, glm::normalize(a->transform.position - b->transform.position)) / invMassSum;
245+
glm::vec3 impulse = glm::normalize(a->transform.position - b->transform.position) * std::max(0.0f, j_mag);
246+
247+
if (a->isDynamic()) {
248+
a->applyImpact(impactPoint, impulse, acceleration.objects[a->geometryIndex]);
249+
a->velocity += impulse / a->mass;
250+
}
251+
if (b->isDynamic()) {
252+
b->applyImpact(impactPoint, -impulse, acceleration.objects[b->geometryIndex]);
253+
b->velocity -= impulse / b->mass;
254+
}
255+
}
256+
}
257+
}
258+
}
259+
}
260+
190261
// UX State Tracking
191262
void updateWindowTitle() {
192263
glm::vec3 pos = camera.getPosition();
@@ -864,6 +935,19 @@ class RacingEngine {
864935
// Update entities
865936
entityManager.update(deltaTime);
866937

938+
// Resolve collisions and apply deformations
939+
resolveCollisions();
940+
941+
// Update Vulkan geometry for deformed objects
942+
for (size_t i = 0; i < entityManager.getEntities().size(); i++) {
943+
auto& entity = entityManager.getEntities()[i];
944+
if (entity->isDeformed) {
945+
acceleration.updateGeometry(physicalDevice, device, graphicsQueue, rayTracing.commandPool, entity->geometryIndex);
946+
entity->isDeformed = false;
947+
accumulationFrames = 0; // Reset accumulation when geometry changes
948+
}
949+
}
950+
867951
// Prepare TLAS instance data (CPU-side, before command buffer recording)
868952
if (entityManager.countDynamic() > 0) {
869953
const auto& transforms = entityManager.getTransforms();

0 commit comments

Comments
 (0)