Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 67 additions & 1 deletion src/Entity.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#pragma once
#include "Transform.h"
#include "VulkanAcceleration.h"
#include <string>
#include <cstdint>
#include <glm/gtc/matrix_inverse.hpp>
#include <algorithm>

enum class EntityType {
STATIC, // Objects that never move (buildings, ground, barriers)
Expand All @@ -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;
Expand All @@ -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);
}
};
145 changes: 144 additions & 1 deletion src/VulkanAcceleration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ void VulkanAcceleration::createGroundPlane(VkPhysicalDevice physicalDevice, VkDe
GeometryObject obj;
obj.vertexCount = static_cast<uint32_t>(vertices.size());
obj.indexCount = static_cast<uint32_t>(indices.size());
obj.cpuVertices = vertices;
obj.cpuIndices = indices;

VkDeviceSize vertexBufferSize = sizeof(Vertex) * vertices.size();
VkDeviceSize indexBufferSize = sizeof(uint32_t) * indices.size();
Expand Down Expand Up @@ -205,6 +207,8 @@ void VulkanAcceleration::createCube(VkPhysicalDevice physicalDevice, VkDevice de
GeometryObject obj;
obj.vertexCount = static_cast<uint32_t>(vertices.size());
obj.indexCount = static_cast<uint32_t>(indices.size());
obj.cpuVertices = vertices;
obj.cpuIndices = indices;

VkDeviceSize vertexBufferSize = sizeof(Vertex) * vertices.size();
VkDeviceSize indexBufferSize = sizeof(uint32_t) * indices.size();
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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, &copyRegion);
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) {
Expand Down
14 changes: 14 additions & 0 deletions src/VulkanAcceleration.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ struct GeometryObject {
uint32_t vertexCount = 0;
uint32_t indexCount = 0;
AccelerationStructure blas;

// CPU copies for deformation/destruction
std::vector<Vertex> cpuVertices;
std::vector<uint32_t> cpuIndices;
};

class VulkanAcceleration {
Expand Down Expand Up @@ -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);

Expand Down
84 changes: 84 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down
Loading