From a073cf4f9f3ed41d6f0adf5aa41e43a829ea7bf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tur=C3=A1nszki=20J=C3=A1nos?= Date: Fri, 30 Jun 2023 09:13:42 +0200 Subject: [PATCH] BVH accelaration support for scene intersections (#704) --- Editor/EmitterWindow.cpp | 2 +- Editor/HairParticleWindow.cpp | 2 +- Editor/MaterialWindow.cpp | 16 +- Editor/MeshWindow.cpp | 35 +- Editor/MeshWindow.h | 1 + Editor/ObjectWindow.cpp | 8 +- WickedEngine/wiBVH.h | 30 +- WickedEngine/wiGraphics.h | 2 + WickedEngine/wiGraphicsDevice_DX12.cpp | 3 + WickedEngine/wiGraphicsDevice_Vulkan.cpp | 3 + WickedEngine/wiHelper.cpp | 23 + WickedEngine/wiHelper.h | 3 + WickedEngine/wiRenderer.cpp | 17 +- WickedEngine/wiScene.cpp | 1243 ++++++++++------------ WickedEngine/wiScene.h | 2 +- WickedEngine/wiScene_Components.cpp | 68 ++ WickedEngine/wiScene_Components.h | 18 + WickedEngine/wiScene_Serializers.cpp | 5 + WickedEngine/wiTerrain.cpp | 7 +- WickedEngine/wiVersion.cpp | 3 +- 20 files changed, 767 insertions(+), 724 deletions(-) diff --git a/Editor/EmitterWindow.cpp b/Editor/EmitterWindow.cpp index 220612607..8e3090930 100644 --- a/Editor/EmitterWindow.cpp +++ b/Editor/EmitterWindow.cpp @@ -762,7 +762,7 @@ void EmitterWindow::UpdateData() std::string ss; ss += "Emitter Mesh: " + (meshName != nullptr ? meshName->name : "NO EMITTER MESH") + " (" + std::to_string(emitter->meshID) + ")\n"; - ss += "Memory usage: " + std::to_string(emitter->GetMemorySizeInBytes() / 1024.0f / 1024.0f) + " MB\n"; + ss += "Memory usage: " + wi::helper::GetMemorySizeText(emitter->GetMemorySizeInBytes()) + "\n"; ss += "\n"; auto data = emitter->GetStatistics(); diff --git a/Editor/HairParticleWindow.cpp b/Editor/HairParticleWindow.cpp index 73b5ebff9..3d87f2ab6 100644 --- a/Editor/HairParticleWindow.cpp +++ b/Editor/HairParticleWindow.cpp @@ -278,7 +278,7 @@ void HairParticleWindow::UpdateData() std::string ss; ss += "To use hair particle system, first you must select a surface mesh to spawn particles on.\n\n"; - ss += "Memory usage: " + std::to_string(hair->GetMemorySizeInBytes() / 1024.0f / 1024.0f) + " MB\n"; + ss += "Memory usage: " + wi::helper::GetMemorySizeText(hair->GetMemorySizeInBytes()) + "\n"; infoLabel.SetText(ss); meshComboBox.ClearItems(); diff --git a/Editor/MaterialWindow.cpp b/Editor/MaterialWindow.cpp index 5ce7fdb6e..ba744d4cf 100644 --- a/Editor/MaterialWindow.cpp +++ b/Editor/MaterialWindow.cpp @@ -1,8 +1,6 @@ #include "stdafx.h" #include "MaterialWindow.h" -#include - using namespace wi::graphics; using namespace wi::ecs; using namespace wi::scene; @@ -654,19 +652,7 @@ void MaterialWindow::Create(EditorComponent* _editor) tooltiptext += "\nMip levels: " + std::to_string(texture.desc.mip_levels); tooltiptext += "\nFormat: "; tooltiptext += GetFormatString(texture.desc.format); - - std::stringstream ss; - ss << std::fixed << std::setprecision(2); - const size_t texture_size = ComputeTextureMemorySizeInBytes(texture.desc); - if (texture_size >= 1024ull * 1024ull) - { - ss << "\nMemory: " << ComputeTextureMemorySizeInBytes(texture.desc) / 1024.0f / 1024.0f << " MB"; - } - else - { - ss << "\nMemory: " << ComputeTextureMemorySizeInBytes(texture.desc) / 1024.0f << " KB"; - } - tooltiptext += ss.str(); + tooltiptext += "\nMemory: " + wi::helper::GetMemorySizeText(ComputeTextureMemorySizeInBytes(texture.desc)); } } diff --git a/Editor/MeshWindow.cpp b/Editor/MeshWindow.cpp index 4a098a25e..7afcc0e86 100644 --- a/Editor/MeshWindow.cpp +++ b/Editor/MeshWindow.cpp @@ -12,7 +12,7 @@ void MeshWindow::Create(EditorComponent* _editor) { editor = _editor; wi::gui::Window::Create(ICON_MESH " Mesh", wi::gui::Window::WindowControls::COLLAPSE | wi::gui::Window::WindowControls::CLOSE); - SetSize(XMFLOAT2(580, 730)); + SetSize(XMFLOAT2(580, 760)); closeButton.SetTooltip("Delete MeshComponent"); OnClose([=](wi::gui::EventArgs args) { @@ -144,6 +144,19 @@ void MeshWindow::Create(EditorComponent* _editor) }); AddWidget(&doubleSidedShadowCheckBox); + bvhCheckBox.Create("Enable BVH: "); + bvhCheckBox.SetTooltip("Whether to generate BVH (Bounding Volume Hierarchy) for the mesh or not.\nBVH will be used to optimize intersections with the mesh at an additional memory cost.\nIt is recommended to use a BVH for high polygon count meshes that will be used for intersections.\nThis CPU BVH does not support skinned or morphed geometry."); + bvhCheckBox.SetSize(XMFLOAT2(hei, hei)); + bvhCheckBox.SetPos(XMFLOAT2(x, y += step)); + bvhCheckBox.OnClick([&](wi::gui::EventArgs args) { + MeshComponent* mesh = editor->GetCurrentScene().meshes.GetComponent(entity); + if (mesh != nullptr) + { + mesh->SetBVHEnabled(args.bValue); + } + }); + AddWidget(&bvhCheckBox); + impostorCreateButton.Create("Create Impostor"); impostorCreateButton.SetTooltip("Create an impostor image of the mesh. The mesh will be replaced by this image when far away, to render faster."); impostorCreateButton.SetSize(XMFLOAT2(wid, hei)); @@ -721,7 +734,21 @@ void MeshWindow::SetEntity(Entity entity, int subset) ss += "Index count: " + std::to_string(mesh->indices.size()) + "\n"; ss += "Index format: " + std::string(wi::graphics::GetIndexBufferFormatString(mesh->GetIndexFormat())) + "\n"; ss += "Subset count: " + std::to_string(mesh->subsets.size()) + " (" + std::to_string(mesh->GetLODCount()) + " LODs)\n"; - ss += "GPU memory: " + std::to_string((mesh->generalBuffer.GetDesc().size + mesh->streamoutBuffer.GetDesc().size) / 1024.0f / 1024.0f) + " MB\n"; + ss += "CPU memory: " + wi::helper::GetMemorySizeText(mesh->GetMemoryUsageCPU()) + "\n"; + if (mesh->bvh.IsValid()) + { + ss += "\tCPU BVH size: " + wi::helper::GetMemorySizeText(mesh->GetMemoryUsageBVH()) + "\n"; + } + ss += "GPU memory: " + wi::helper::GetMemorySizeText(mesh->GetMemoryUsageGPU()) + "\n"; + if (!mesh->BLASes.empty()) + { + size_t size = 0; + for (auto& x : mesh->BLASes) + { + size += x.size; + } + ss += "\tBLAS size: " + wi::helper::GetMemorySizeText(size) + "\n"; + } ss += "\nVertex buffers:\n"; if (!mesh->vertex_positions.empty()) ss += "\tposition;\n"; if (!mesh->vertex_normals.empty()) ss += "\tnormal;\n"; @@ -732,7 +759,7 @@ void MeshWindow::SetEntity(Entity entity, int subset) if (mesh->so_pre.IsValid()) ss += "\tprevious_position;\n"; if (mesh->vb_bon.IsValid()) ss += "\tbone;\n"; if (mesh->vb_tan.IsValid()) ss += "\ttangent;\n"; - if (mesh->so_pos_nor_wind.IsValid()) ss += "\tstreamout_position;\n"; + if (mesh->so_pos_nor_wind.IsValid()) ss += "\tstreamout_position_normal_wind;\n"; if (mesh->so_tan.IsValid()) ss += "\tstreamout_tangents;\n"; meshInfoLabel.SetText(ss); @@ -776,6 +803,7 @@ void MeshWindow::SetEntity(Entity entity, int subset) doubleSidedCheckBox.SetCheck(mesh->IsDoubleSided()); doubleSidedShadowCheckBox.SetCheck(mesh->IsDoubleSidedShadow()); + bvhCheckBox.SetCheck(mesh->bvh.IsValid()); const ImpostorComponent* impostor = scene.impostors.GetComponent(entity); if (impostor != nullptr) @@ -865,6 +893,7 @@ void MeshWindow::ResizeLayout() add(subsetMaterialComboBox); add_right(doubleSidedCheckBox); add_right(doubleSidedShadowCheckBox); + add_right(bvhCheckBox); add_fullwidth(impostorCreateButton); add(impostorDistanceSlider); add(tessellationFactorSlider); diff --git a/Editor/MeshWindow.h b/Editor/MeshWindow.h index 34428c931..69c3d5fc4 100644 --- a/Editor/MeshWindow.h +++ b/Editor/MeshWindow.h @@ -17,6 +17,7 @@ public: wi::gui::ComboBox subsetMaterialComboBox; wi::gui::CheckBox doubleSidedCheckBox; wi::gui::CheckBox doubleSidedShadowCheckBox; + wi::gui::CheckBox bvhCheckBox; wi::gui::Button impostorCreateButton; wi::gui::Slider impostorDistanceSlider; wi::gui::Slider tessellationFactorSlider; diff --git a/Editor/ObjectWindow.cpp b/Editor/ObjectWindow.cpp index 814bf7183..2fc228cdc 100644 --- a/Editor/ObjectWindow.cpp +++ b/Editor/ObjectWindow.cpp @@ -334,7 +334,7 @@ void ObjectWindow::Create(EditorComponent* _editor) AddWidget(&shadowCheckBox); navmeshCheckBox.Create("Navmesh: "); - navmeshCheckBox.SetTooltip("Set object to be a navigation mesh filtering (FILTER_NAVIGATION_MESH)."); + navmeshCheckBox.SetTooltip("Set object to be a navigation mesh filtering (FILTER_NAVIGATION_MESH).\nTurning on navmesh also enables BVH for the underlying mesh."); navmeshCheckBox.SetSize(XMFLOAT2(hei, hei)); navmeshCheckBox.SetPos(XMFLOAT2(x, y += step)); navmeshCheckBox.SetCheck(true); @@ -348,6 +348,12 @@ void ObjectWindow::Create(EditorComponent* _editor) if (args.bValue) { object->filterMask |= wi::enums::FILTER_NAVIGATION_MESH; + + MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); + if (mesh != nullptr) + { + mesh->SetBVHEnabled(args.bValue); + } } else { diff --git a/WickedEngine/wiBVH.h b/WickedEngine/wiBVH.h index 92c165f6e..fed1aedf6 100644 --- a/WickedEngine/wiBVH.h +++ b/WickedEngine/wiBVH.h @@ -21,7 +21,8 @@ namespace wi uint32_t node_count = 0; uint32_t* leaf_indices = nullptr; uint32_t leaf_count = 0; - const wi::primitive::AABB* leaf_aabb_data = nullptr; + + constexpr bool IsValid() const { return nodes != nullptr; } void Build(const wi::primitive::AABB* aabbs, uint32_t aabb_count) { @@ -37,21 +38,19 @@ namespace wi nodes = (Node*)allocation.data(); leaf_indices = (uint32_t*)(nodes + node_capacity); leaf_count = aabb_count; - leaf_aabb_data = aabbs; Node& node = nodes[node_count++]; - node.aabb = {}; - node.offset = 0; + node = {}; node.count = aabb_count; for (uint32_t i = 0; i < aabb_count; ++i) { node.aabb = wi::primitive::AABB::Merge(node.aabb, aabbs[i]); leaf_indices[i] = i; } - Subdivide(0); + Subdivide(0, aabbs); } - void Subdivide(uint32_t nodeIndex) + void Subdivide(uint32_t nodeIndex, const wi::primitive::AABB* leaf_aabb_data) { Node& node = nodes[nodeIndex]; if (node.count <= 2) @@ -91,20 +90,22 @@ namespace wi uint32_t left_child_index = node_count++; uint32_t right_child_index = node_count++; node.left = left_child_index; + nodes[left_child_index] = {}; nodes[left_child_index].offset = node.offset; nodes[left_child_index].count = leftCount; + nodes[right_child_index] = {}; nodes[right_child_index].offset = i; nodes[right_child_index].count = node.count - leftCount; node.count = 0; - UpdateNodeBounds(left_child_index); - UpdateNodeBounds(right_child_index); + UpdateNodeBounds(left_child_index, leaf_aabb_data); + UpdateNodeBounds(right_child_index, leaf_aabb_data); // recurse - Subdivide(left_child_index); - Subdivide(right_child_index); + Subdivide(left_child_index, leaf_aabb_data); + Subdivide(right_child_index, leaf_aabb_data); } - void UpdateNodeBounds(uint32_t nodeIndex) + void UpdateNodeBounds(uint32_t nodeIndex, const wi::primitive::AABB* leaf_aabb_data) { Node& node = nodes[nodeIndex]; node.aabb = {}; @@ -121,14 +122,17 @@ namespace wi const T& primitive, uint32_t nodeIndex, const std::function& callback - ) + ) const { Node& node = nodes[nodeIndex]; if (!node.aabb.intersects(primitive)) return; if (node.isLeaf()) { - callback(leaf_indices[node.offset]); + for (uint32_t i = 0; i < node.count; ++i) + { + callback(leaf_indices[node.offset + i]); + } } else { diff --git a/WickedEngine/wiGraphics.h b/WickedEngine/wiGraphics.h index af10a8df8..3b8c641c0 100644 --- a/WickedEngine/wiGraphics.h +++ b/WickedEngine/wiGraphics.h @@ -1181,6 +1181,8 @@ namespace wi::graphics { RaytracingAccelerationStructureDesc desc; + size_t size = 0; + constexpr const RaytracingAccelerationStructureDesc& GetDesc() const { return desc; } }; diff --git a/WickedEngine/wiGraphicsDevice_DX12.cpp b/WickedEngine/wiGraphicsDevice_DX12.cpp index 3c71c23ab..cd3311943 100644 --- a/WickedEngine/wiGraphicsDevice_DX12.cpp +++ b/WickedEngine/wiGraphicsDevice_DX12.cpp @@ -4209,6 +4209,7 @@ using namespace dx12_internal; bvh->internal_state = internal_state; bvh->type = GPUResource::Type::RAYTRACING_ACCELERATION_STRUCTURE; bvh->desc = *desc; + bvh->size = 0; if (desc->flags & RaytracingAccelerationStructureDesc::FLAG_ALLOW_UPDATE) { @@ -4334,6 +4335,8 @@ using namespace dx12_internal; GPUBufferDesc scratch_desc; scratch_desc.size = (uint32_t)std::max(internal_state->info.ScratchDataSizeInBytes, internal_state->info.UpdateScratchDataSizeInBytes); + bvh->size = alignedSize + scratch_desc.size; + return CreateBuffer(&scratch_desc, nullptr, &internal_state->scratch); } bool GraphicsDevice_DX12::CreateRaytracingPipelineState(const RaytracingPipelineStateDesc* desc, RaytracingPipelineState* rtpso) const diff --git a/WickedEngine/wiGraphicsDevice_Vulkan.cpp b/WickedEngine/wiGraphicsDevice_Vulkan.cpp index 62de0e343..25ea61f37 100644 --- a/WickedEngine/wiGraphicsDevice_Vulkan.cpp +++ b/WickedEngine/wiGraphicsDevice_Vulkan.cpp @@ -5704,6 +5704,7 @@ using namespace vulkan_internal; bvh->internal_state = internal_state; bvh->type = GPUResource::Type::RAYTRACING_ACCELERATION_STRUCTURE; bvh->desc = *desc; + bvh->size = 0; internal_state->buildInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR; internal_state->buildInfo.flags = 0; @@ -5885,6 +5886,8 @@ using namespace vulkan_internal; ); #endif + bvh->size = bufferInfo.size; + return res == VK_SUCCESS; } bool GraphicsDevice_Vulkan::CreateRaytracingPipelineState(const RaytracingPipelineStateDesc* desc, RaytracingPipelineState* rtpso) const diff --git a/WickedEngine/wiHelper.cpp b/WickedEngine/wiHelper.cpp index 2cc0d7d0e..972d2b226 100644 --- a/WickedEngine/wiHelper.cpp +++ b/WickedEngine/wiHelper.cpp @@ -1327,4 +1327,27 @@ namespace wi::helper #endif // _WIN32 return mem; } + + std::string GetMemorySizeText(size_t sizeInBytes) + { + std::stringstream ss; + ss << std::fixed << std::setprecision(1); + if (sizeInBytes >= 1024ull * 1024ull * 1024ull) + { + ss << (double)sizeInBytes / 1024.0 / 1024.0 / 1024.0 << " GB"; + } + else if (sizeInBytes >= 1024ull * 1024ull) + { + ss << (double)sizeInBytes / 1024.0 / 1024.0 << " MB"; + } + else if (sizeInBytes >= 1024ull) + { + ss << (double)sizeInBytes / 1024.0 << " KB"; + } + else + { + ss << sizeInBytes << " bytes"; + } + return ss.str(); + } } diff --git a/WickedEngine/wiHelper.h b/WickedEngine/wiHelper.h index 6dad51b51..4edea8053 100644 --- a/WickedEngine/wiHelper.h +++ b/WickedEngine/wiHelper.h @@ -150,4 +150,7 @@ namespace wi::helper uint64_t process_virtual = 0; // size of currently mapped virtual memory by application (in bytes) }; MemoryUsage GetMemoryUsage(); + + // Returns a good looking memory size string as either bytes, KB, MB or GB + std::string GetMemorySizeText(size_t sizeInBytes); }; diff --git a/WickedEngine/wiRenderer.cpp b/WickedEngine/wiRenderer.cpp index 900d17a0b..d8a1085d1 100644 --- a/WickedEngine/wiRenderer.cpp +++ b/WickedEngine/wiRenderer.cpp @@ -5911,7 +5911,6 @@ void DrawDebugWorld( if (debugPartitionTree) { - // Actually, there is no SPTree any more, so this will just render all aabbs... device->EventBegin("DebugPartitionTree", cmd); device->BindPipelineState(&PSO_debug[DEBUGRENDERING_CUBE], cmd); @@ -5929,6 +5928,22 @@ void DrawDebugWorld( for (size_t i = 0; i < scene.aabb_objects.size(); ++i) { + //const ObjectComponent& object = scene.objects[i]; + //const MeshComponent* mesh = scene.meshes.GetComponent(object.meshID); + //if (mesh != nullptr && !mesh->bvh_leaf_aabbs.empty()) + //{ + // const XMMATRIX objectMat = XMLoadFloat4x4(&scene.matrix_objects[i]); + // for (const AABB& aabb : mesh->bvh_leaf_aabbs) + // { + // XMStoreFloat4x4(&sb.g_xTransform, aabb.getAsBoxMatrix() * objectMat * camera.GetViewProjection()); + // sb.g_xColor = XMFLOAT4(1, 1, 0, 1); + + // device->BindDynamicConstantBuffer(sb, CB_GETBINDSLOT(MiscCB), cmd); + + // device->DrawIndexed(24, 0, 0, cmd); + // } + //} + const AABB& aabb = scene.aabb_objects[i]; XMStoreFloat4x4(&sb.g_xTransform, aabb.getAsBoxMatrix()*camera.GetViewProjection()); diff --git a/WickedEngine/wiScene.cpp b/WickedEngine/wiScene.cpp index 6ce7fac5f..029e61fbd 100644 --- a/WickedEngine/wiScene.cpp +++ b/WickedEngine/wiScene.cpp @@ -2959,16 +2959,10 @@ namespace wi::scene wi::jobsystem::Wait(ctx); collider_count_cpu = collider_allocator_cpu.load(); collider_count_gpu = collider_allocator_gpu.load(); - + collider_bvh.Build(aabb_colliders_cpu, collider_count_cpu); // Springs: const XMVECTOR windDir = XMLoadFloat3(&weather.windDirection); - - if (springs.GetCount() > 0) - { - spring_collider_bvh.Build(aabb_colliders_cpu, collider_count_cpu); - } - for (size_t i = 0; i < springs.GetCount(); ++i) { SpringComponent& spring = springs[i]; @@ -3094,7 +3088,7 @@ namespace wi::scene if (colliders_cpu != nullptr) { - spring_collider_bvh.Intersects(tail_sphere, 0, [&](uint32_t collider_index) { + collider_bvh.Intersects(tail_sphere, 0, [&](uint32_t collider_index) { const ColliderComponent& collider = colliders_cpu[collider_index]; float dist = 0; @@ -3513,7 +3507,7 @@ namespace wi::scene total_size += ComputeTextureMemorySizeInBytes(impostorRenderTarget_Albedo_MSAA.desc); total_size += ComputeTextureMemorySizeInBytes(impostorRenderTarget_Surface_MSAA.desc); total_size += ComputeTextureMemorySizeInBytes(impostorRenderTarget_Normal_MSAA.desc); - info += "\n\tMemory = " + std::to_string(total_size / 1024.0f / 1024.0f) + " MB\n"; + info += "\n\tMemory = " + wi::helper::GetMemorySizeText(total_size) + "\n"; wi::backlog::post(info); } @@ -4066,7 +4060,7 @@ namespace wi::scene total_size += ComputeTextureMemorySizeInBytes(envrenderingDepthBuffer_MSAA.desc); total_size += ComputeTextureMemorySizeInBytes(envrenderingColorBuffer_MSAA.desc); total_size += ComputeTextureMemorySizeInBytes(envmapArray.desc); - info += "\n\tMemory = " + std::to_string(total_size / 1024.0f / 1024.0f) + " MB\n"; + info += "\n\tMemory = " + wi::helper::GetMemorySizeText(total_size) + "\n"; wi::backlog::post(info); } @@ -4540,47 +4534,17 @@ namespace wi::scene Scene::RayIntersectionResult Scene::Intersects(const Ray& ray, uint32_t filterMask, uint32_t layerMask, uint32_t lod) const { - // Set up parallel closest hit selection: - uint8_t stack_mem[1024 * 8]; - wi::allocator::LinearAllocator allocator; - allocator.init(stack_mem, sizeof(stack_mem)); - wi::jobsystem::context ctx; - struct JobDataForFunction + RayIntersectionResult result; + + const XMVECTOR rayOrigin = XMLoadFloat3(&ray.origin); + const XMVECTOR rayDirection = XMVector3Normalize(XMLoadFloat3(&ray.direction)); + + if ((filterMask & FILTER_COLLIDER) && collider_bvh.IsValid()) { - RayIntersectionResult* groupResults; - uint32_t layerMask; - Ray ray; - XMVECTOR rayOrigin; - XMVECTOR rayDirection; - float TMin; - float TMax; - } jobDataFunction; - const uint32_t threadCount = wi::jobsystem::GetThreadCount(); - jobDataFunction.groupResults = (RayIntersectionResult*)allocator.allocate(AlignTo(sizeof(RayIntersectionResult) * threadCount, 16)); - const size_t allocator_reserved_begin = allocator.offset; - for (uint32_t t = 0; t < threadCount; ++t) - { - jobDataFunction.groupResults[t] = RayIntersectionResult(); - } - jobDataFunction.layerMask = layerMask; - jobDataFunction.ray = ray; - jobDataFunction.rayOrigin = XMLoadFloat3(&ray.origin); - jobDataFunction.rayDirection = XMVector3Normalize(XMLoadFloat3(&ray.direction)); - jobDataFunction.TMin = ray.TMin; - jobDataFunction.TMax = ray.TMax; + collider_bvh.Intersects(ray, 0, [&](uint32_t collider_index) { + const ColliderComponent& collider = colliders_cpu[collider_index]; - if (filterMask & FILTER_COLLIDER) - { - const uint32_t jobCount = collider_count_cpu; - const uint32_t groupSize = wi::jobsystem::DispatchGroupCount(jobCount, threadCount); - wi::jobsystem::Dispatch(ctx, jobCount, groupSize, [&jobDataFunction, this](wi::jobsystem::JobArgs args) { - - if (!aabb_colliders_cpu[args.jobIndex].intersects(jobDataFunction.ray)) - return; - - const ColliderComponent& collider = colliders_cpu[args.jobIndex]; - - if ((collider.layerMask & jobDataFunction.layerMask) == 0) + if ((collider.layerMask & layerMask) == 0) return; float dist = 0; @@ -4591,34 +4555,33 @@ namespace wi::scene { default: case ColliderComponent::Shape::Sphere: - intersects = jobDataFunction.ray.intersects(collider.sphere, dist, direction); + intersects = ray.intersects(collider.sphere, dist, direction); break; case ColliderComponent::Shape::Capsule: - intersects = jobDataFunction.ray.intersects(collider.capsule, dist, direction); + intersects = ray.intersects(collider.capsule, dist, direction); break; case ColliderComponent::Shape::Plane: - intersects = jobDataFunction.ray.intersects(collider.plane, dist, direction); + intersects = ray.intersects(collider.plane, dist, direction); break; } if (intersects) { - RayIntersectionResult& groupResult = jobDataFunction.groupResults[args.groupID]; - if (dist < groupResult.distance) + if (dist < result.distance) { - groupResult.distance = dist; - groupResult.bary = {}; - groupResult.entity = colliders.GetEntity(args.jobIndex); - groupResult.normal = direction; - groupResult.velocity = {}; - XMStoreFloat3(&groupResult.position, jobDataFunction.rayOrigin + jobDataFunction.rayDirection * dist); - groupResult.subsetIndex = -1; - groupResult.vertexID0 = 0; - groupResult.vertexID1 = 0; - groupResult.vertexID2 = 0; + result.distance = dist; + result.bary = {}; + result.entity = colliders.GetEntity(collider_index); + result.normal = direction; + result.velocity = {}; + XMStoreFloat3(&result.position, rayOrigin + rayDirection * dist); + result.subsetIndex = -1; + result.vertexID0 = 0; + result.vertexID1 = 0; + result.vertexID2 = 0; } } - }); + }); } if (filterMask & FILTER_OBJECT_ALL) @@ -4639,130 +4602,115 @@ namespace wi::scene if (mesh == nullptr) continue; - struct JobDataForInstance + const Entity entity = objects.GetEntity(objectIndex); + const SoftBodyPhysicsComponent* softbody = softbodies.GetComponent(object.meshID); + const XMMATRIX objectMat = XMLoadFloat4x4(&matrix_objects[objectIndex]); + const XMMATRIX objectMatPrev = XMLoadFloat4x4(&matrix_objects_prev[objectIndex]); + const XMMATRIX objectMat_Inverse = XMMatrixInverse(nullptr, objectMat); + const XMVECTOR rayOrigin_local = XMVector3Transform(rayOrigin, objectMat_Inverse); + const XMVECTOR rayDirection_local = XMVector3Normalize(XMVector3TransformNormal(rayDirection, objectMat_Inverse)); + const ArmatureComponent* armature = mesh->IsSkinned() ? armatures.GetComponent(mesh->armatureID) : nullptr; + + auto intersect_triangle = [&](uint32_t subsetIndex, uint32_t indexOffset, uint32_t triangleIndex) { - JobDataForFunction* func; - Entity entity; - const MeshComponent* mesh; - const SoftBodyPhysicsComponent* softbody; - const ArmatureComponent* armature; - XMMATRIX objectMat; - XMMATRIX objectMatPrev; - XMVECTOR rayOrigin_local; - XMVECTOR rayDirection_local; - }; + const uint32_t i0 = mesh->indices[indexOffset + triangleIndex * 3 + 0]; + const uint32_t i1 = mesh->indices[indexOffset + triangleIndex * 3 + 1]; + const uint32_t i2 = mesh->indices[indexOffset + triangleIndex * 3 + 2]; - uint8_t* jobdata_allocation = allocator.allocate(AlignTo(sizeof(JobDataForInstance), 16)); - if (jobdata_allocation == nullptr) - { - // Flush pending jobs, reset temp allocations, and reuse: - wi::jobsystem::Wait(ctx); - allocator.offset = allocator_reserved_begin; - jobdata_allocation = allocator.allocate(AlignTo(sizeof(JobDataForInstance), 16)); - } - JobDataForInstance& jobData = *(JobDataForInstance*)jobdata_allocation; - jobData.func = &jobDataFunction; - jobData.mesh = mesh; - jobData.entity = objects.GetEntity(objectIndex); - jobData.softbody = softbodies.GetComponent(object.meshID); - jobData.objectMat = XMLoadFloat4x4(&matrix_objects[objectIndex]); - jobData.objectMatPrev = XMLoadFloat4x4(&matrix_objects_prev[objectIndex]); - const XMMATRIX objectMat_Inverse = XMMatrixInverse(nullptr, jobData.objectMat); - jobData.rayOrigin_local = XMVector3Transform(jobData.func->rayOrigin, objectMat_Inverse); - jobData.rayDirection_local = XMVector3Normalize(XMVector3TransformNormal(jobData.func->rayDirection, objectMat_Inverse)); - jobData.armature = jobData.mesh->IsSkinned() ? armatures.GetComponent(jobData.mesh->armatureID) : nullptr; + XMVECTOR p0; + XMVECTOR p1; + XMVECTOR p2; - uint32_t first_subset = 0; - uint32_t last_subset = 0; - jobData.mesh->GetLODSubsetRange(lod, first_subset, last_subset); - for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex) - { - const MeshComponent::MeshSubset& subset = jobData.mesh->subsets[subsetIndex]; - if (subset.indexCount == 0) - continue; - const uint32_t indexOffset = subset.indexOffset; - - // Parallel closest hit selection: - const uint32_t jobCount = subset.indexCount / 3; - const uint32_t groupSize = wi::jobsystem::DispatchGroupCount(jobCount, threadCount); - wi::jobsystem::Dispatch(ctx, jobCount, groupSize, [&jobData, subsetIndex, indexOffset](wi::jobsystem::JobArgs args) { - - RayIntersectionResult& groupResult = jobData.func->groupResults[args.groupID]; - - const uint32_t i0 = jobData.mesh->indices[indexOffset + args.jobIndex * 3 + 0]; - const uint32_t i1 = jobData.mesh->indices[indexOffset + args.jobIndex * 3 + 1]; - const uint32_t i2 = jobData.mesh->indices[indexOffset + args.jobIndex * 3 + 2]; - - XMVECTOR p0; - XMVECTOR p1; - XMVECTOR p2; - - const bool softbody_active = jobData.softbody != nullptr && !jobData.softbody->vertex_positions_simulation.empty(); - if (softbody_active) + const bool softbody_active = softbody != nullptr && !softbody->vertex_positions_simulation.empty(); + if (softbody_active) + { + p0 = softbody->vertex_positions_simulation[i0].LoadPOS(); + p1 = softbody->vertex_positions_simulation[i1].LoadPOS(); + p2 = softbody->vertex_positions_simulation[i2].LoadPOS(); + } + else + { + if (armature == nullptr || armature->boneData.empty()) { - p0 = jobData.softbody->vertex_positions_simulation[i0].LoadPOS(); - p1 = jobData.softbody->vertex_positions_simulation[i1].LoadPOS(); - p2 = jobData.softbody->vertex_positions_simulation[i2].LoadPOS(); + p0 = XMLoadFloat3(&mesh->vertex_positions[i0]); + p1 = XMLoadFloat3(&mesh->vertex_positions[i1]); + p2 = XMLoadFloat3(&mesh->vertex_positions[i2]); } else { - if (jobData.armature == nullptr || jobData.armature->boneData.empty()) - { - p0 = XMLoadFloat3(&jobData.mesh->vertex_positions[i0]); - p1 = XMLoadFloat3(&jobData.mesh->vertex_positions[i1]); - p2 = XMLoadFloat3(&jobData.mesh->vertex_positions[i2]); - } - else - { - p0 = SkinVertex(*jobData.mesh, *jobData.armature, i0); - p1 = SkinVertex(*jobData.mesh, *jobData.armature, i1); - p2 = SkinVertex(*jobData.mesh, *jobData.armature, i2); - } + p0 = SkinVertex(*mesh, *armature, i0); + p1 = SkinVertex(*mesh, *armature, i1); + p2 = SkinVertex(*mesh, *armature, i2); } + } - float distance; - XMFLOAT2 bary; - if (wi::math::RayTriangleIntersects(jobData.rayOrigin_local, jobData.rayDirection_local, p0, p1, p2, distance, bary)) + float distance; + XMFLOAT2 bary; + if (wi::math::RayTriangleIntersects(rayOrigin_local, rayDirection_local, p0, p1, p2, distance, bary)) + { + const XMVECTOR pos_local = XMVectorAdd(rayOrigin_local, rayDirection_local * distance); + const XMVECTOR pos = XMVector3Transform(pos_local, objectMat); + distance = wi::math::Distance(pos, rayOrigin); + + // Note: we do the TMin, Tmax check here, in world space! We use the RayTriangleIntersects in local space, so we don't use those in there + if (distance < result.distance && distance >= ray.TMin && distance <= ray.TMax) { - const XMVECTOR pos_local = XMVectorAdd(jobData.rayOrigin_local, jobData.rayDirection_local * distance); - const XMVECTOR pos = XMVector3Transform(pos_local, jobData.objectMat); - distance = wi::math::Distance(pos, jobData.func->rayOrigin); + const XMVECTOR nor = XMVector3Normalize(XMVector3TransformNormal(XMVector3Cross(XMVectorSubtract(p2, p1), XMVectorSubtract(p1, p0)), objectMat)); + const XMVECTOR vel = pos - XMVector3Transform(pos_local, objectMatPrev); - // Note: we do the TMin, Tmax check here, in world space! We use the RayTriangleIntersects in local space, so we don't use those in there - if (distance < groupResult.distance && distance >= jobData.func->TMin && distance <= jobData.func->TMax) - { - const XMVECTOR nor = XMVector3Normalize(XMVector3TransformNormal(XMVector3Cross(XMVectorSubtract(p2, p1), XMVectorSubtract(p1, p0)), jobData.objectMat)); - const XMVECTOR vel = pos - XMVector3Transform(pos_local, jobData.objectMatPrev); - - groupResult.entity = jobData.entity; - XMStoreFloat3(&groupResult.position, pos); - XMStoreFloat3(&groupResult.normal, nor); - XMStoreFloat3(&groupResult.velocity, vel); - groupResult.distance = distance; - groupResult.subsetIndex = (int)subsetIndex; - groupResult.vertexID0 = (int)i0; - groupResult.vertexID1 = (int)i1; - groupResult.vertexID2 = (int)i2; - groupResult.bary = bary; - } + result.entity = entity; + XMStoreFloat3(&result.position, pos); + XMStoreFloat3(&result.normal, nor); + XMStoreFloat3(&result.velocity, vel); + result.distance = distance; + result.subsetIndex = (int)subsetIndex; + result.vertexID0 = (int)i0; + result.vertexID1 = (int)i1; + result.vertexID2 = (int)i2; + result.bary = bary; } - }); + } + }; + + if (mesh->bvh.IsValid()) + { + Ray ray_local = Ray(rayOrigin_local, rayDirection_local); + + mesh->bvh.Intersects(ray_local, 0, [&](uint32_t index) { + const uint32_t userdata = mesh->bvh_leaf_aabbs[index].userdata; + const uint32_t triangleIndex = userdata & 0xFFFFFF; + const uint32_t subsetIndex = userdata >> 24u; + const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex]; + if (subset.indexCount == 0) + return; + const uint32_t indexOffset = subset.indexOffset; + intersect_triangle(subsetIndex, indexOffset, triangleIndex); + }); + } + else + { + // Brute-force interection test: + uint32_t first_subset = 0; + uint32_t last_subset = 0; + mesh->GetLODSubsetRange(lod, first_subset, last_subset); + for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex) + { + const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex]; + if (subset.indexCount == 0) + continue; + const uint32_t indexOffset = subset.indexOffset; + const uint32_t triangleCount = subset.indexCount / 3; + + for (uint32_t triangleIndex = 0; triangleIndex < triangleCount; ++triangleIndex) + { + intersect_triangle(subsetIndex, indexOffset, triangleIndex); + } + } } } } - // Merge thread results: - wi::jobsystem::Wait(ctx); - RayIntersectionResult& result = jobDataFunction.groupResults[0]; - for (uint32_t t = 1; t < threadCount; ++t) - { - if (jobDataFunction.groupResults[t].distance < result.distance) - { - result = jobDataFunction.groupResults[t]; - } - } - // Construct a matrix that will orient to position (P) according to surface normal (N): XMVECTOR N = XMLoadFloat3(&result.normal); XMVECTOR P = XMLoadFloat3(&result.position); @@ -4776,45 +4724,18 @@ namespace wi::scene } Scene::SphereIntersectionResult Scene::Intersects(const Sphere& sphere, uint32_t filterMask, uint32_t layerMask, uint32_t lod) const { - // Set up parallel closest hit selection: - uint8_t stack_mem[1024 * 8]; - wi::allocator::LinearAllocator allocator; - allocator.init(stack_mem, sizeof(stack_mem)); - wi::jobsystem::context ctx; - struct JobDataForFunction + SphereIntersectionResult result; + + const XMVECTOR Center = XMLoadFloat3(&sphere.center); + const XMVECTOR Radius = XMVectorReplicate(sphere.radius); + const XMVECTOR RadiusSq = XMVectorMultiply(Radius, Radius); + + if ((filterMask & FILTER_COLLIDER) && collider_bvh.IsValid()) { - SphereIntersectionResult* groupResults; - uint32_t layerMask; - Sphere sphere; - XMVECTOR Center; - XMVECTOR Radius; - XMVECTOR RadiusSq; - } jobDataFunction; - const uint32_t threadCount = wi::jobsystem::GetThreadCount(); - jobDataFunction.groupResults = (SphereIntersectionResult*)allocator.allocate(AlignTo(sizeof(SphereIntersectionResult) * threadCount, 16)); - const size_t allocator_reserved_begin = allocator.offset; - for (uint32_t t = 0; t < threadCount; ++t) - { - jobDataFunction.groupResults[t] = SphereIntersectionResult(); - } - jobDataFunction.layerMask = layerMask; - jobDataFunction.sphere = sphere; - jobDataFunction.Center = XMLoadFloat3(&sphere.center); - jobDataFunction.Radius = XMVectorReplicate(sphere.radius); - jobDataFunction.RadiusSq = XMVectorMultiply(jobDataFunction.Radius, jobDataFunction.Radius); + collider_bvh.Intersects(sphere, 0, [&](uint32_t collider_index) { + const ColliderComponent& collider = colliders_cpu[collider_index]; - if (filterMask & FILTER_COLLIDER) - { - const uint32_t jobCount = collider_count_cpu; - const uint32_t groupSize = wi::jobsystem::DispatchGroupCount(jobCount, threadCount); - wi::jobsystem::Dispatch(ctx, jobCount, groupSize, [&jobDataFunction, this](wi::jobsystem::JobArgs args) { - - if (!aabb_colliders_cpu[args.jobIndex].intersects(jobDataFunction.sphere)) - return; - - const ColliderComponent& collider = colliders_cpu[args.jobIndex]; - - if ((collider.layerMask & jobDataFunction.layerMask) == 0) + if ((collider.layerMask & layerMask) == 0) return; float dist = 0; @@ -4826,27 +4747,26 @@ namespace wi::scene { default: case ColliderComponent::Shape::Sphere: - intersects = jobDataFunction.sphere.intersects(collider.sphere, dist, direction); + intersects = sphere.intersects(collider.sphere, dist, direction); XMStoreFloat3(&position, XMLoadFloat3(&collider.sphere.center) + XMLoadFloat3(&direction) * dist); break; case ColliderComponent::Shape::Capsule: - intersects = jobDataFunction.sphere.intersects(collider.capsule, dist, direction); + intersects = sphere.intersects(collider.capsule, dist, direction); break; case ColliderComponent::Shape::Plane: - intersects = jobDataFunction.sphere.intersects(collider.plane, dist, direction); + intersects = sphere.intersects(collider.plane, dist, direction); break; } if (intersects) { - CapsuleIntersectionResult& groupResult = jobDataFunction.groupResults[args.groupID]; - if (dist > groupResult.depth) + if (dist > result.depth) { - groupResult.depth = dist; - groupResult.entity = colliders.GetEntity(args.jobIndex); - groupResult.normal = direction; - groupResult.position = position; - groupResult.velocity = {}; + result.depth = dist; + result.entity = colliders.GetEntity(collider_index); + result.normal = direction; + result.position = position; + result.velocity = {}; } } }); @@ -4870,213 +4790,204 @@ namespace wi::scene if (mesh == nullptr) continue; - struct JobDataForInstance + const Entity entity = objects.GetEntity(objectIndex); + const SoftBodyPhysicsComponent* softbody = softbodies.GetComponent(object.meshID); + const XMMATRIX objectMat = XMLoadFloat4x4(&matrix_objects[objectIndex]); + const XMMATRIX objectMatPrev = XMLoadFloat4x4(&matrix_objects_prev[objectIndex]); + const XMMATRIX objectMatInverse = XMMatrixInverse(nullptr, objectMat); + const ArmatureComponent* armature = mesh->IsSkinned() ? armatures.GetComponent(mesh->armatureID) : nullptr; + + auto intersect_triangle = [&](uint32_t subsetIndex, uint32_t indexOffset, uint32_t triangleIndex) { - JobDataForFunction* func; - Entity entity; - const MeshComponent* mesh; - const SoftBodyPhysicsComponent* softbody; - const ArmatureComponent* armature; - XMMATRIX objectMat; - XMMATRIX objectMatPrev; - }; + const uint32_t i0 = mesh->indices[indexOffset + triangleIndex * 3 + 0]; + const uint32_t i1 = mesh->indices[indexOffset + triangleIndex * 3 + 1]; + const uint32_t i2 = mesh->indices[indexOffset + triangleIndex * 3 + 2]; - uint8_t* jobdata_allocation = allocator.allocate(AlignTo(sizeof(JobDataForInstance), 16)); - if (jobdata_allocation == nullptr) - { - // Flush pending jobs, reset temp allocations, and reuse: - wi::jobsystem::Wait(ctx); - allocator.offset = allocator_reserved_begin; - jobdata_allocation = allocator.allocate(AlignTo(sizeof(JobDataForInstance), 16)); - } - JobDataForInstance& jobData = *(JobDataForInstance*)jobdata_allocation; - jobData.func = &jobDataFunction; - jobData.mesh = mesh; - jobData.entity = objects.GetEntity(objectIndex); - jobData.softbody = softbodies.GetComponent(object.meshID); - jobData.objectMat = XMLoadFloat4x4(&matrix_objects[objectIndex]); - jobData.objectMatPrev = XMLoadFloat4x4(&matrix_objects_prev[objectIndex]); - jobData.armature = jobData.mesh->IsSkinned() ? armatures.GetComponent(jobData.mesh->armatureID) : nullptr; + XMVECTOR p0; + XMVECTOR p1; + XMVECTOR p2; - uint32_t first_subset = 0; - uint32_t last_subset = 0; - jobData.mesh->GetLODSubsetRange(lod, first_subset, last_subset); - for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex) - { - const MeshComponent::MeshSubset& subset = jobData.mesh->subsets[subsetIndex]; - if (subset.indexCount == 0) - continue; - const uint32_t indexOffset = subset.indexOffset; - - // Parallel closest hit selection: - const uint32_t jobCount = subset.indexCount / 3; - const uint32_t groupSize = wi::jobsystem::DispatchGroupCount(jobCount, threadCount); - wi::jobsystem::Dispatch(ctx, jobCount, groupSize, [&jobData, subsetIndex, indexOffset](wi::jobsystem::JobArgs args) { - - SceneIntersectSphereResult& groupResult = jobData.func->groupResults[args.groupID]; - - const uint32_t i0 = jobData.mesh->indices[indexOffset + args.jobIndex * 3 + 0]; - const uint32_t i1 = jobData.mesh->indices[indexOffset + args.jobIndex * 3 + 1]; - const uint32_t i2 = jobData.mesh->indices[indexOffset + args.jobIndex * 3 + 2]; - - XMVECTOR p0; - XMVECTOR p1; - XMVECTOR p2; - - const bool softbody_active = jobData.softbody != nullptr && !jobData.softbody->vertex_positions_simulation.empty(); - if (softbody_active) + const bool softbody_active = softbody != nullptr && !softbody->vertex_positions_simulation.empty(); + if (softbody_active) + { + p0 = softbody->vertex_positions_simulation[i0].LoadPOS(); + p1 = softbody->vertex_positions_simulation[i1].LoadPOS(); + p2 = softbody->vertex_positions_simulation[i2].LoadPOS(); + } + else + { + if (armature == nullptr || armature->boneData.empty()) { - p0 = jobData.softbody->vertex_positions_simulation[i0].LoadPOS(); - p1 = jobData.softbody->vertex_positions_simulation[i1].LoadPOS(); - p2 = jobData.softbody->vertex_positions_simulation[i2].LoadPOS(); + p0 = XMLoadFloat3(&mesh->vertex_positions[i0]); + p1 = XMLoadFloat3(&mesh->vertex_positions[i1]); + p2 = XMLoadFloat3(&mesh->vertex_positions[i2]); } else { - if (jobData.armature == nullptr || jobData.armature->boneData.empty()) - { - p0 = XMLoadFloat3(&jobData.mesh->vertex_positions[i0]); - p1 = XMLoadFloat3(&jobData.mesh->vertex_positions[i1]); - p2 = XMLoadFloat3(&jobData.mesh->vertex_positions[i2]); - } - else - { - p0 = SkinVertex(*jobData.mesh, *jobData.armature, i0); - p1 = SkinVertex(*jobData.mesh, *jobData.armature, i1); - p2 = SkinVertex(*jobData.mesh, *jobData.armature, i2); - } + p0 = SkinVertex(*mesh, *armature, i0); + p1 = SkinVertex(*mesh, *armature, i1); + p2 = SkinVertex(*mesh, *armature, i2); } + } - p0 = XMVector3Transform(p0, jobData.objectMat); - p1 = XMVector3Transform(p1, jobData.objectMat); - p2 = XMVector3Transform(p2, jobData.objectMat); + p0 = XMVector3Transform(p0, objectMat); + p1 = XMVector3Transform(p1, objectMat); + p2 = XMVector3Transform(p2, objectMat); - XMFLOAT3 min, max; - XMStoreFloat3(&min, XMVectorMin(p0, XMVectorMin(p1, p2))); - XMStoreFloat3(&max, XMVectorMax(p0, XMVectorMax(p1, p2))); - AABB aabb_triangle(min, max); - if (jobData.func->sphere.intersects(aabb_triangle) == AABB::OUTSIDE) - return; + XMFLOAT3 min, max; + XMStoreFloat3(&min, XMVectorMin(p0, XMVectorMin(p1, p2))); + XMStoreFloat3(&max, XMVectorMax(p0, XMVectorMax(p1, p2))); + AABB aabb_triangle(min, max); + if (sphere.intersects(aabb_triangle) == AABB::OUTSIDE) + return; - // Compute the plane of the triangle (has to be normalized). - XMVECTOR N = XMVector3Normalize(XMVector3Cross(XMVectorSubtract(p1, p0), XMVectorSubtract(p2, p0))); + // Compute the plane of the triangle (has to be normalized). + XMVECTOR N = XMVector3Normalize(XMVector3Cross(XMVectorSubtract(p1, p0), XMVectorSubtract(p2, p0))); - // Assert that the triangle is not degenerate. - assert(!XMVector3Equal(N, XMVectorZero())); + // Assert that the triangle is not degenerate. + assert(!XMVector3Equal(N, XMVectorZero())); - // Find the nearest feature on the triangle to the sphere. - XMVECTOR Dist = XMVector3Dot(XMVectorSubtract(jobData.func->Center, p0), N); + // Find the nearest feature on the triangle to the sphere. + XMVECTOR Dist = XMVector3Dot(XMVectorSubtract(Center, p0), N); - if (!jobData.mesh->IsDoubleSided() && XMVectorGetX(Dist) > 0) - return; // pass through back faces + if (!mesh->IsDoubleSided() && XMVectorGetX(Dist) > 0) + return; // pass through back faces - // If the center of the sphere is farther from the plane of the triangle than - // the radius of the sphere, then there cannot be an intersection. - XMVECTOR NoIntersection = XMVectorLess(Dist, XMVectorNegate(jobData.func->Radius)); - NoIntersection = XMVectorOrInt(NoIntersection, XMVectorGreater(Dist, jobData.func->Radius)); + // If the center of the sphere is farther from the plane of the triangle than + // the radius of the sphere, then there cannot be an intersection. + XMVECTOR NoIntersection = XMVectorLess(Dist, XMVectorNegate(Radius)); + NoIntersection = XMVectorOrInt(NoIntersection, XMVectorGreater(Dist, Radius)); - // Project the center of the sphere onto the plane of the triangle. - XMVECTOR Point0 = XMVectorNegativeMultiplySubtract(N, Dist, jobData.func->Center); + // Project the center of the sphere onto the plane of the triangle. + XMVECTOR Point0 = XMVectorNegativeMultiplySubtract(N, Dist, Center); - // Is it inside all the edges? If so we intersect because the distance - // to the plane is less than the radius. - //XMVECTOR Intersection = DirectX::Internal::PointOnPlaneInsideTriangle(Point0, p0, p1, p2); + // Is it inside all the edges? If so we intersect because the distance + // to the plane is less than the radius. + //XMVECTOR Intersection = DirectX::Internal::PointOnPlaneInsideTriangle(Point0, p0, p1, p2); - // Compute the cross products of the vector from the base of each edge to - // the point with each edge vector. - XMVECTOR C0 = XMVector3Cross(XMVectorSubtract(Point0, p0), XMVectorSubtract(p1, p0)); - XMVECTOR C1 = XMVector3Cross(XMVectorSubtract(Point0, p1), XMVectorSubtract(p2, p1)); - XMVECTOR C2 = XMVector3Cross(XMVectorSubtract(Point0, p2), XMVectorSubtract(p0, p2)); + // Compute the cross products of the vector from the base of each edge to + // the point with each edge vector. + XMVECTOR C0 = XMVector3Cross(XMVectorSubtract(Point0, p0), XMVectorSubtract(p1, p0)); + XMVECTOR C1 = XMVector3Cross(XMVectorSubtract(Point0, p1), XMVectorSubtract(p2, p1)); + XMVECTOR C2 = XMVector3Cross(XMVectorSubtract(Point0, p2), XMVectorSubtract(p0, p2)); - // If the cross product points in the same direction as the normal the the - // point is inside the edge (it is zero if is on the edge). - XMVECTOR Zero = XMVectorZero(); - XMVECTOR Inside0 = XMVectorLessOrEqual(XMVector3Dot(C0, N), Zero); - XMVECTOR Inside1 = XMVectorLessOrEqual(XMVector3Dot(C1, N), Zero); - XMVECTOR Inside2 = XMVectorLessOrEqual(XMVector3Dot(C2, N), Zero); + // If the cross product points in the same direction as the normal the the + // point is inside the edge (it is zero if is on the edge). + XMVECTOR Zero = XMVectorZero(); + XMVECTOR Inside0 = XMVectorLessOrEqual(XMVector3Dot(C0, N), Zero); + XMVECTOR Inside1 = XMVectorLessOrEqual(XMVector3Dot(C1, N), Zero); + XMVECTOR Inside2 = XMVectorLessOrEqual(XMVector3Dot(C2, N), Zero); - // If the point inside all of the edges it is inside. - XMVECTOR Intersection = XMVectorAndInt(XMVectorAndInt(Inside0, Inside1), Inside2); + // If the point inside all of the edges it is inside. + XMVECTOR Intersection = XMVectorAndInt(XMVectorAndInt(Inside0, Inside1), Inside2); - bool inside = XMVector4EqualInt(XMVectorAndCInt(Intersection, NoIntersection), XMVectorTrueInt()); + bool inside = XMVector4EqualInt(XMVectorAndCInt(Intersection, NoIntersection), XMVectorTrueInt()); - // Find the nearest point on each edge. + // Find the nearest point on each edge. - // Edge 0,1 - XMVECTOR Point1 = DirectX::Internal::PointOnLineSegmentNearestPoint(p0, p1, jobData.func->Center); + // Edge 0,1 + XMVECTOR Point1 = DirectX::Internal::PointOnLineSegmentNearestPoint(p0, p1, Center); - // If the distance to the center of the sphere to the point is less than - // the radius of the sphere then it must intersect. - Intersection = XMVectorOrInt(Intersection, XMVectorLessOrEqual(XMVector3LengthSq(XMVectorSubtract(jobData.func->Center, Point1)), jobData.func->RadiusSq)); + // If the distance to the center of the sphere to the point is less than + // the radius of the sphere then it must intersect. + Intersection = XMVectorOrInt(Intersection, XMVectorLessOrEqual(XMVector3LengthSq(XMVectorSubtract(Center, Point1)), RadiusSq)); - // Edge 1,2 - XMVECTOR Point2 = DirectX::Internal::PointOnLineSegmentNearestPoint(p1, p2, jobData.func->Center); + // Edge 1,2 + XMVECTOR Point2 = DirectX::Internal::PointOnLineSegmentNearestPoint(p1, p2, Center); - // If the distance to the center of the sphere to the point is less than - // the radius of the sphere then it must intersect. - Intersection = XMVectorOrInt(Intersection, XMVectorLessOrEqual(XMVector3LengthSq(XMVectorSubtract(jobData.func->Center, Point2)), jobData.func->RadiusSq)); + // If the distance to the center of the sphere to the point is less than + // the radius of the sphere then it must intersect. + Intersection = XMVectorOrInt(Intersection, XMVectorLessOrEqual(XMVector3LengthSq(XMVectorSubtract(Center, Point2)), RadiusSq)); - // Edge 2,0 - XMVECTOR Point3 = DirectX::Internal::PointOnLineSegmentNearestPoint(p2, p0, jobData.func->Center); + // Edge 2,0 + XMVECTOR Point3 = DirectX::Internal::PointOnLineSegmentNearestPoint(p2, p0, Center); - // If the distance to the center of the sphere to the point is less than - // the radius of the sphere then it must intersect. - Intersection = XMVectorOrInt(Intersection, XMVectorLessOrEqual(XMVector3LengthSq(XMVectorSubtract(jobData.func->Center, Point3)), jobData.func->RadiusSq)); + // If the distance to the center of the sphere to the point is less than + // the radius of the sphere then it must intersect. + Intersection = XMVectorOrInt(Intersection, XMVectorLessOrEqual(XMVector3LengthSq(XMVectorSubtract(Center, Point3)), RadiusSq)); - bool intersects = XMVector4EqualInt(XMVectorAndCInt(Intersection, NoIntersection), XMVectorTrueInt()); + bool intersects = XMVector4EqualInt(XMVectorAndCInt(Intersection, NoIntersection), XMVectorTrueInt()); - if (intersects) + if (intersects) + { + XMVECTOR bestPoint = Point0; + if (!inside) { - XMVECTOR bestPoint = Point0; - if (!inside) - { - // If the sphere center's projection on the triangle plane is not within the triangle, - // determine the closest point on triangle to the sphere center - float bestDist = XMVectorGetX(XMVector3LengthSq(Point1 - jobData.func->Center)); - bestPoint = Point1; + // If the sphere center's projection on the triangle plane is not within the triangle, + // determine the closest point on triangle to the sphere center + float bestDist = XMVectorGetX(XMVector3LengthSq(Point1 - Center)); + bestPoint = Point1; - float d = XMVectorGetX(XMVector3LengthSq(Point2 - jobData.func->Center)); - if (d < bestDist) - { - bestDist = d; - bestPoint = Point2; - } - d = XMVectorGetX(XMVector3LengthSq(Point3 - jobData.func->Center)); - if (d < bestDist) - { - bestDist = d; - bestPoint = Point3; - } + float d = XMVectorGetX(XMVector3LengthSq(Point2 - Center)); + if (d < bestDist) + { + bestDist = d; + bestPoint = Point2; } - XMVECTOR intersectionVec = jobData.func->Center - bestPoint; - XMVECTOR intersectionVecLen = XMVector3Length(intersectionVec); - - float depth = jobData.func->sphere.radius - XMVectorGetX(intersectionVecLen); - if (depth > groupResult.depth) + d = XMVectorGetX(XMVector3LengthSq(Point3 - Center)); + if (d < bestDist) { - groupResult.entity = jobData.entity; - groupResult.depth = depth; - XMStoreFloat3(&groupResult.position, bestPoint); - XMStoreFloat3(&groupResult.normal, intersectionVec / intersectionVecLen); - - XMMATRIX objectMatInverse = XMMatrixInverse(nullptr, jobData.objectMat); - XMVECTOR vel = bestPoint - XMVector3Transform(XMVector3Transform(bestPoint, objectMatInverse), jobData.objectMatPrev); - XMStoreFloat3(&groupResult.velocity, vel); + bestDist = d; + bestPoint = Point3; } } + XMVECTOR intersectionVec = Center - bestPoint; + XMVECTOR intersectionVecLen = XMVector3Length(intersectionVec); + + float depth = sphere.radius - XMVectorGetX(intersectionVecLen); + if (depth > result.depth) + { + result.entity = entity; + result.depth = depth; + XMStoreFloat3(&result.position, bestPoint); + XMStoreFloat3(&result.normal, intersectionVec / intersectionVecLen); + + XMVECTOR vel = bestPoint - XMVector3Transform(XMVector3Transform(bestPoint, objectMatInverse), objectMatPrev); + XMStoreFloat3(&result.velocity, vel); + } + } + }; + + if (mesh->bvh.IsValid()) + { + XMFLOAT3 center_local; + float radius_local; + XMStoreFloat3(¢er_local, XMVector3Transform(XMLoadFloat3(&sphere.center), objectMatInverse)); + XMStoreFloat(&radius_local, XMVector3TransformNormal(XMLoadFloat(&sphere.radius), objectMatInverse)); + Sphere sphere_local = Sphere(center_local, radius_local); + + mesh->bvh.Intersects(sphere_local, 0, [&](uint32_t index) { + const uint32_t userdata = mesh->bvh_leaf_aabbs[index].userdata; + const uint32_t triangleIndex = userdata & 0xFFFFFF; + const uint32_t subsetIndex = userdata >> 24u; + const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex]; + if (subset.indexCount == 0) + return; + const uint32_t indexOffset = subset.indexOffset; + intersect_triangle(subsetIndex, indexOffset, triangleIndex); }); } + else + { + // Brute-force interection test: + uint32_t first_subset = 0; + uint32_t last_subset = 0; + mesh->GetLODSubsetRange(lod, first_subset, last_subset); + for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex) + { + const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex]; + if (subset.indexCount == 0) + continue; + const uint32_t indexOffset = subset.indexOffset; + const uint32_t triangleCount = subset.indexCount / 3; - } - } + for (uint32_t triangleIndex = 0; triangleIndex < triangleCount; ++triangleIndex) + { + intersect_triangle(subsetIndex, indexOffset, triangleIndex); + } + } + } - // Merge thread results: - wi::jobsystem::Wait(ctx); - SphereIntersectionResult& result = jobDataFunction.groupResults[0]; - for (uint32_t t = 1; t < threadCount; ++t) - { - if (jobDataFunction.groupResults[t].depth > result.depth) - { - result = jobDataFunction.groupResults[t]; } } @@ -5084,55 +4995,23 @@ namespace wi::scene } Scene::CapsuleIntersectionResult Scene::Intersects(const Capsule& capsule, uint32_t filterMask, uint32_t layerMask, uint32_t lod) const { - // Set up parallel closest hit selection: - uint8_t stack_mem[1024 * 8]; - wi::allocator::LinearAllocator allocator; - allocator.init(stack_mem, sizeof(stack_mem)); - wi::jobsystem::context ctx; - struct JobDataForFunction + CapsuleIntersectionResult result; + + const XMVECTOR Base = XMLoadFloat3(&capsule.base); + const XMVECTOR Tip = XMLoadFloat3(&capsule.tip); + const XMVECTOR Radius = XMVectorReplicate(capsule.radius); + const XMVECTOR LineEndOffset = XMVector3Normalize(Tip - Base) * Radius; + const XMVECTOR A = Base + LineEndOffset; + const XMVECTOR B = Tip - LineEndOffset; + const XMVECTOR RadiusSq = XMVectorMultiply(Radius, Radius); + const AABB capsule_aabb = capsule.getAABB(); + + if ((filterMask & FILTER_COLLIDER) && collider_bvh.IsValid()) { - CapsuleIntersectionResult* groupResults; - Capsule capsule; - uint32_t layerMask; - XMVECTOR Base; - XMVECTOR Tip; - XMVECTOR Radius; - XMVECTOR LineEndOffset; - XMVECTOR A; - XMVECTOR B; - XMVECTOR RadiusSq; - AABB capsule_aabb; - } jobDataFunction; - const uint32_t threadCount = wi::jobsystem::GetThreadCount(); - jobDataFunction.groupResults = (CapsuleIntersectionResult*)allocator.allocate(AlignTo(sizeof(CapsuleIntersectionResult) * threadCount, 16)); - const size_t allocator_reserved_begin = allocator.offset; - for (uint32_t t = 0; t < threadCount; ++t) - { - jobDataFunction.groupResults[t] = CapsuleIntersectionResult(); - } - jobDataFunction.layerMask = layerMask; - jobDataFunction.capsule = capsule; - jobDataFunction.Base = XMLoadFloat3(&capsule.base); - jobDataFunction.Tip = XMLoadFloat3(&capsule.tip); - jobDataFunction.Radius = XMVectorReplicate(capsule.radius); - jobDataFunction.LineEndOffset = XMVector3Normalize(jobDataFunction.Tip - jobDataFunction.Base) * jobDataFunction.Radius; - jobDataFunction.A = jobDataFunction.Base + jobDataFunction.LineEndOffset; - jobDataFunction.B = jobDataFunction.Tip - jobDataFunction.LineEndOffset; - jobDataFunction.RadiusSq = XMVectorMultiply(jobDataFunction.Radius, jobDataFunction.Radius); - jobDataFunction.capsule_aabb = capsule.getAABB(); + collider_bvh.Intersects(capsule_aabb, 0, [&](uint32_t collider_index) { + const ColliderComponent& collider = colliders_cpu[collider_index]; - if (filterMask & FILTER_COLLIDER) - { - const uint32_t jobCount = collider_count_cpu; - const uint32_t groupSize = wi::jobsystem::DispatchGroupCount(jobCount, threadCount); - wi::jobsystem::Dispatch(ctx, jobCount, groupSize, [&jobDataFunction, this](wi::jobsystem::JobArgs args) { - - if (!aabb_colliders_cpu[args.jobIndex].intersects(jobDataFunction.capsule_aabb)) - return; - - const ColliderComponent& collider = colliders_cpu[args.jobIndex]; - - if ((collider.layerMask & jobDataFunction.layerMask) == 0) + if ((collider.layerMask & layerMask) == 0) return; float dist = 0; @@ -5144,30 +5023,29 @@ namespace wi::scene { default: case ColliderComponent::Shape::Sphere: - intersects = jobDataFunction.capsule.intersects(collider.sphere, dist, direction); + intersects = capsule.intersects(collider.sphere, dist, direction); XMStoreFloat3(&position, XMLoadFloat3(&collider.sphere.center) + XMLoadFloat3(&direction) * dist); break; case ColliderComponent::Shape::Capsule: - intersects = jobDataFunction.capsule.intersects(collider.capsule, position, direction, dist); + intersects = capsule.intersects(collider.capsule, position, direction, dist); break; case ColliderComponent::Shape::Plane: - intersects = jobDataFunction.capsule.intersects(collider.plane, dist, direction); + intersects = capsule.intersects(collider.plane, dist, direction); break; } if (intersects) { - CapsuleIntersectionResult& groupResult = jobDataFunction.groupResults[args.groupID]; - if (dist > groupResult.depth) + if (dist > result.depth) { - groupResult.depth = dist; - groupResult.entity = colliders.GetEntity(args.jobIndex); - groupResult.normal = direction; - groupResult.position = position; - groupResult.velocity = {}; + result.depth = dist; + result.entity = colliders.GetEntity(collider_index); + result.normal = direction; + result.position = position; + result.velocity = {}; } } - }); + }); } if (filterMask & FILTER_OBJECT_ALL) @@ -5175,7 +5053,7 @@ namespace wi::scene for (size_t objectIndex = 0; objectIndex < aabb_objects.size(); ++objectIndex) { const AABB& aabb = aabb_objects[objectIndex]; - if (jobDataFunction.capsule_aabb.intersects(aabb) == AABB::INTERSECTION_TYPE::OUTSIDE || (layerMask & aabb.layerMask) == 0) + if (capsule_aabb.intersects(aabb) == AABB::INTERSECTION_TYPE::OUTSIDE || (layerMask & aabb.layerMask) == 0) continue; const ObjectComponent& object = objects[objectIndex]; @@ -5189,193 +5067,79 @@ namespace wi::scene if (mesh == nullptr) continue; - struct JobDataForInstance + const Entity entity = objects.GetEntity(objectIndex); + const SoftBodyPhysicsComponent* softbody = softbodies.GetComponent(object.meshID); + const XMMATRIX objectMat = XMLoadFloat4x4(&matrix_objects[objectIndex]); + const XMMATRIX objectMatPrev = XMLoadFloat4x4(&matrix_objects_prev[objectIndex]); + const ArmatureComponent* armature = mesh->IsSkinned() ? armatures.GetComponent(mesh->armatureID) : nullptr; + const XMMATRIX objectMat_Inverse = XMMatrixInverse(nullptr, objectMat); + + auto intersect_triangle = [&](uint32_t subsetIndex, uint32_t indexOffset, uint32_t triangleIndex) { - JobDataForFunction* func; - Entity entity; - const MeshComponent* mesh; - const SoftBodyPhysicsComponent* softbody; - const ArmatureComponent* armature; - XMMATRIX objectMat; - XMMATRIX objectMatPrev; - }; + const uint32_t i0 = mesh->indices[indexOffset + triangleIndex * 3 + 0]; + const uint32_t i1 = mesh->indices[indexOffset + triangleIndex * 3 + 1]; + const uint32_t i2 = mesh->indices[indexOffset + triangleIndex * 3 + 2]; - uint8_t* jobdata_allocation = allocator.allocate(AlignTo(sizeof(JobDataForInstance), 16)); - if (jobdata_allocation == nullptr) - { - // Flush pending jobs, reset temp allocations, and reuse: - wi::jobsystem::Wait(ctx); - allocator.offset = allocator_reserved_begin; - jobdata_allocation = allocator.allocate(AlignTo(sizeof(JobDataForInstance), 16)); - } - JobDataForInstance& jobData = *(JobDataForInstance*)jobdata_allocation; - jobData.func = &jobDataFunction; - jobData.mesh = mesh; - jobData.entity = objects.GetEntity(objectIndex); - jobData.softbody = softbodies.GetComponent(object.meshID); - jobData.objectMat = XMLoadFloat4x4(&matrix_objects[objectIndex]); - jobData.objectMatPrev = XMLoadFloat4x4(&matrix_objects_prev[objectIndex]); - jobData.armature = jobData.mesh->IsSkinned() ? armatures.GetComponent(jobData.mesh->armatureID) : nullptr; + XMVECTOR p0; + XMVECTOR p1; + XMVECTOR p2; - uint32_t first_subset = 0; - uint32_t last_subset = 0; - jobData.mesh->GetLODSubsetRange(lod, first_subset, last_subset); - for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex) - { - const MeshComponent::MeshSubset& subset = jobData.mesh->subsets[subsetIndex]; - if (subset.indexCount == 0) - continue; - const uint32_t indexOffset = subset.indexOffset; - - // Parallel closest hit selection: - const uint32_t jobCount = subset.indexCount / 3; - const uint32_t groupSize = wi::jobsystem::DispatchGroupCount(jobCount, threadCount); - wi::jobsystem::Dispatch(ctx, jobCount, groupSize, [&jobData, subsetIndex, indexOffset](wi::jobsystem::JobArgs args) { - - SceneIntersectSphereResult& groupResult = jobData.func->groupResults[args.groupID]; - - const uint32_t i0 = jobData.mesh->indices[indexOffset + args.jobIndex * 3 + 0]; - const uint32_t i1 = jobData.mesh->indices[indexOffset + args.jobIndex * 3 + 1]; - const uint32_t i2 = jobData.mesh->indices[indexOffset + args.jobIndex * 3 + 2]; - - XMVECTOR p0; - XMVECTOR p1; - XMVECTOR p2; - - const bool softbody_active = jobData.softbody != nullptr && !jobData.softbody->vertex_positions_simulation.empty(); - if (softbody_active) + const bool softbody_active = softbody != nullptr && !softbody->vertex_positions_simulation.empty(); + if (softbody_active) + { + p0 = softbody->vertex_positions_simulation[i0].LoadPOS(); + p1 = softbody->vertex_positions_simulation[i1].LoadPOS(); + p2 = softbody->vertex_positions_simulation[i2].LoadPOS(); + } + else + { + if (armature == nullptr || armature->boneData.empty()) { - p0 = jobData.softbody->vertex_positions_simulation[i0].LoadPOS(); - p1 = jobData.softbody->vertex_positions_simulation[i1].LoadPOS(); - p2 = jobData.softbody->vertex_positions_simulation[i2].LoadPOS(); + p0 = XMLoadFloat3(&mesh->vertex_positions[i0]); + p1 = XMLoadFloat3(&mesh->vertex_positions[i1]); + p2 = XMLoadFloat3(&mesh->vertex_positions[i2]); } else { - if (jobData.armature == nullptr || jobData.armature->boneData.empty()) - { - p0 = XMLoadFloat3(&jobData.mesh->vertex_positions[i0]); - p1 = XMLoadFloat3(&jobData.mesh->vertex_positions[i1]); - p2 = XMLoadFloat3(&jobData.mesh->vertex_positions[i2]); - } - else - { - p0 = SkinVertex(*jobData.mesh, *jobData.armature, i0); - p1 = SkinVertex(*jobData.mesh, *jobData.armature, i1); - p2 = SkinVertex(*jobData.mesh, *jobData.armature, i2); - } + p0 = SkinVertex(*mesh, *armature, i0); + p1 = SkinVertex(*mesh, *armature, i1); + p2 = SkinVertex(*mesh, *armature, i2); } + } - p0 = XMVector3Transform(p0, jobData.objectMat); - p1 = XMVector3Transform(p1, jobData.objectMat); - p2 = XMVector3Transform(p2, jobData.objectMat); + p0 = XMVector3Transform(p0, objectMat); + p1 = XMVector3Transform(p1, objectMat); + p2 = XMVector3Transform(p2, objectMat); - XMFLOAT3 min, max; - XMStoreFloat3(&min, XMVectorMin(p0, XMVectorMin(p1, p2))); - XMStoreFloat3(&max, XMVectorMax(p0, XMVectorMax(p1, p2))); - AABB aabb_triangle(min, max); - if (jobData.func->capsule_aabb.intersects(aabb_triangle) == AABB::OUTSIDE) - return; + XMFLOAT3 min, max; + XMStoreFloat3(&min, XMVectorMin(p0, XMVectorMin(p1, p2))); + XMStoreFloat3(&max, XMVectorMax(p0, XMVectorMax(p1, p2))); + AABB aabb_triangle(min, max); + if (capsule_aabb.intersects(aabb_triangle) == AABB::OUTSIDE) + return; - // Compute the plane of the triangle (has to be normalized). - XMVECTOR N = XMVector3Normalize(XMVector3Cross(XMVectorSubtract(p1, p0), XMVectorSubtract(p2, p0))); + // Compute the plane of the triangle (has to be normalized). + XMVECTOR N = XMVector3Normalize(XMVector3Cross(XMVectorSubtract(p1, p0), XMVectorSubtract(p2, p0))); - XMVECTOR ReferencePoint; - XMVECTOR d = XMVector3Normalize(jobData.func->B - jobData.func->A); - if (std::abs(XMVectorGetX(XMVector3Dot(N, d))) < std::numeric_limits::epsilon()) - { - // Capsule line cannot be intersected with triangle plane (they are parallel) - // In this case, just take a point from triangle - ReferencePoint = p0; - } - else - { - // Intersect capsule line with triangle plane: - XMVECTOR t = XMVector3Dot(N, (jobData.func->Base - p0) / XMVectorAbs(XMVector3Dot(N, d))); - XMVECTOR LinePlaneIntersection = jobData.func->Base + d * t; - - // Compute the cross products of the vector from the base of each edge to - // the point with each edge vector. - XMVECTOR C0 = XMVector3Cross(XMVectorSubtract(LinePlaneIntersection, p0), XMVectorSubtract(p1, p0)); - XMVECTOR C1 = XMVector3Cross(XMVectorSubtract(LinePlaneIntersection, p1), XMVectorSubtract(p2, p1)); - XMVECTOR C2 = XMVector3Cross(XMVectorSubtract(LinePlaneIntersection, p2), XMVectorSubtract(p0, p2)); - - // If the cross product points in the same direction as the normal the the - // point is inside the edge (it is zero if is on the edge). - XMVECTOR Zero = XMVectorZero(); - XMVECTOR Inside0 = XMVectorLessOrEqual(XMVector3Dot(C0, N), Zero); - XMVECTOR Inside1 = XMVectorLessOrEqual(XMVector3Dot(C1, N), Zero); - XMVECTOR Inside2 = XMVectorLessOrEqual(XMVector3Dot(C2, N), Zero); - - // If the point inside all of the edges it is inside. - XMVECTOR Intersection = XMVectorAndInt(XMVectorAndInt(Inside0, Inside1), Inside2); - - bool inside = XMVectorGetIntX(Intersection) != 0; - - if (inside) - { - ReferencePoint = LinePlaneIntersection; - } - else - { - // Find the nearest point on each edge. - - // Edge 0,1 - XMVECTOR Point1 = wi::math::ClosestPointOnLineSegment(p0, p1, LinePlaneIntersection); - - // Edge 1,2 - XMVECTOR Point2 = wi::math::ClosestPointOnLineSegment(p1, p2, LinePlaneIntersection); - - // Edge 2,0 - XMVECTOR Point3 = wi::math::ClosestPointOnLineSegment(p2, p0, LinePlaneIntersection); - - ReferencePoint = Point1; - float bestDist = XMVectorGetX(XMVector3LengthSq(Point1 - LinePlaneIntersection)); - float d = abs(XMVectorGetX(XMVector3LengthSq(Point2 - LinePlaneIntersection))); - if (d < bestDist) - { - bestDist = d; - ReferencePoint = Point2; - } - d = abs(XMVectorGetX(XMVector3LengthSq(Point3 - LinePlaneIntersection))); - if (d < bestDist) - { - bestDist = d; - ReferencePoint = Point3; - } - } - - - } - - // Place a sphere on closest point on line segment to intersection: - XMVECTOR Center = wi::math::ClosestPointOnLineSegment(jobData.func->A, jobData.func->B, ReferencePoint); - - // Assert that the triangle is not degenerate. - assert(!XMVector3Equal(N, XMVectorZero())); - - // Find the nearest feature on the triangle to the sphere. - XMVECTOR Dist = XMVector3Dot(XMVectorSubtract(Center, p0), N); - - if (!jobData.mesh->IsDoubleSided() && XMVectorGetX(Dist) > 0) - return; // pass through back faces - - // If the center of the sphere is farther from the plane of the triangle than - // the radius of the sphere, then there cannot be an intersection. - XMVECTOR NoIntersection = XMVectorLess(Dist, XMVectorNegate(jobData.func->Radius)); - NoIntersection = XMVectorOrInt(NoIntersection, XMVectorGreater(Dist, jobData.func->Radius)); - - // Project the center of the sphere onto the plane of the triangle. - XMVECTOR Point0 = XMVectorNegativeMultiplySubtract(N, Dist, Center); - - // Is it inside all the edges? If so we intersect because the distance - // to the plane is less than the radius. - //XMVECTOR Intersection = DirectX::Internal::PointOnPlaneInsideTriangle(Point0, p0, p1, p2); + XMVECTOR ReferencePoint; + XMVECTOR d = XMVector3Normalize(B - A); + if (std::abs(XMVectorGetX(XMVector3Dot(N, d))) < std::numeric_limits::epsilon()) + { + // Capsule line cannot be intersected with triangle plane (they are parallel) + // In this case, just take a point from triangle + ReferencePoint = p0; + } + else + { + // Intersect capsule line with triangle plane: + XMVECTOR t = XMVector3Dot(N, (Base - p0) / XMVectorAbs(XMVector3Dot(N, d))); + XMVECTOR LinePlaneIntersection = Base + d * t; // Compute the cross products of the vector from the base of each edge to // the point with each edge vector. - XMVECTOR C0 = XMVector3Cross(XMVectorSubtract(Point0, p0), XMVectorSubtract(p1, p0)); - XMVECTOR C1 = XMVector3Cross(XMVectorSubtract(Point0, p1), XMVectorSubtract(p2, p1)); - XMVECTOR C2 = XMVector3Cross(XMVectorSubtract(Point0, p2), XMVectorSubtract(p0, p2)); + XMVECTOR C0 = XMVector3Cross(XMVectorSubtract(LinePlaneIntersection, p0), XMVectorSubtract(p1, p0)); + XMVECTOR C1 = XMVector3Cross(XMVectorSubtract(LinePlaneIntersection, p1), XMVectorSubtract(p2, p1)); + XMVECTOR C2 = XMVector3Cross(XMVectorSubtract(LinePlaneIntersection, p2), XMVectorSubtract(p0, p2)); // If the cross product points in the same direction as the normal the the // point is inside the edge (it is zero if is on the edge). @@ -5387,86 +5151,193 @@ namespace wi::scene // If the point inside all of the edges it is inside. XMVECTOR Intersection = XMVectorAndInt(XMVectorAndInt(Inside0, Inside1), Inside2); - bool inside = XMVector4EqualInt(XMVectorAndCInt(Intersection, NoIntersection), XMVectorTrueInt()); + bool inside = XMVectorGetIntX(Intersection) != 0; - // Find the nearest point on each edge. - - // Edge 0,1 - XMVECTOR Point1 = wi::math::ClosestPointOnLineSegment(p0, p1, Center); - - // If the distance to the center of the sphere to the point is less than - // the radius of the sphere then it must intersect. - Intersection = XMVectorOrInt(Intersection, XMVectorLessOrEqual(XMVector3LengthSq(XMVectorSubtract(Center, Point1)), jobData.func->RadiusSq)); - - // Edge 1,2 - XMVECTOR Point2 = wi::math::ClosestPointOnLineSegment(p1, p2, Center); - - // If the distance to the center of the sphere to the point is less than - // the radius of the sphere then it must intersect. - Intersection = XMVectorOrInt(Intersection, XMVectorLessOrEqual(XMVector3LengthSq(XMVectorSubtract(Center, Point2)), jobData.func->RadiusSq)); - - // Edge 2,0 - XMVECTOR Point3 = wi::math::ClosestPointOnLineSegment(p2, p0, Center); - - // If the distance to the center of the sphere to the point is less than - // the radius of the sphere then it must intersect. - Intersection = XMVectorOrInt(Intersection, XMVectorLessOrEqual(XMVector3LengthSq(XMVectorSubtract(Center, Point3)), jobData.func->RadiusSq)); - - bool intersects = XMVector4EqualInt(XMVectorAndCInt(Intersection, NoIntersection), XMVectorTrueInt()); - - if (intersects) + if (inside) { - XMVECTOR bestPoint = Point0; - if (!inside) - { - // If the sphere center's projection on the triangle plane is not within the triangle, - // determine the closest point on triangle to the sphere center - float bestDist = XMVectorGetX(XMVector3LengthSq(Point1 - Center)); - bestPoint = Point1; + ReferencePoint = LinePlaneIntersection; + } + else + { + // Find the nearest point on each edge. - float d = XMVectorGetX(XMVector3LengthSq(Point2 - Center)); - if (d < bestDist) - { - bestDist = d; - bestPoint = Point2; - } - d = XMVectorGetX(XMVector3LengthSq(Point3 - Center)); - if (d < bestDist) - { - bestDist = d; - bestPoint = Point3; - } + // Edge 0,1 + XMVECTOR Point1 = wi::math::ClosestPointOnLineSegment(p0, p1, LinePlaneIntersection); + + // Edge 1,2 + XMVECTOR Point2 = wi::math::ClosestPointOnLineSegment(p1, p2, LinePlaneIntersection); + + // Edge 2,0 + XMVECTOR Point3 = wi::math::ClosestPointOnLineSegment(p2, p0, LinePlaneIntersection); + + ReferencePoint = Point1; + float bestDist = XMVectorGetX(XMVector3LengthSq(Point1 - LinePlaneIntersection)); + float d = abs(XMVectorGetX(XMVector3LengthSq(Point2 - LinePlaneIntersection))); + if (d < bestDist) + { + bestDist = d; + ReferencePoint = Point2; } - XMVECTOR intersectionVec = Center - bestPoint; - XMVECTOR intersectionVecLen = XMVector3Length(intersectionVec); - - float depth = jobData.func->capsule.radius - XMVectorGetX(intersectionVecLen); - if (depth > groupResult.depth) + d = abs(XMVectorGetX(XMVector3LengthSq(Point3 - LinePlaneIntersection))); + if (d < bestDist) { - groupResult.entity = jobData.entity; - groupResult.depth = depth; - XMStoreFloat3(&groupResult.position, bestPoint); - XMStoreFloat3(&groupResult.normal, intersectionVec / intersectionVecLen); - - XMMATRIX objectMatInverse = XMMatrixInverse(nullptr, jobData.objectMat); - XMVECTOR vel = bestPoint - XMVector3Transform(XMVector3Transform(bestPoint, objectMatInverse), jobData.objectMatPrev); - XMStoreFloat3(&groupResult.velocity, vel); + bestDist = d; + ReferencePoint = Point3; } } + + + } + + // Place a sphere on closest point on line segment to intersection: + XMVECTOR Center = wi::math::ClosestPointOnLineSegment(A, B, ReferencePoint); + + // Assert that the triangle is not degenerate. + assert(!XMVector3Equal(N, XMVectorZero())); + + // Find the nearest feature on the triangle to the sphere. + XMVECTOR Dist = XMVector3Dot(XMVectorSubtract(Center, p0), N); + + if (!mesh->IsDoubleSided() && XMVectorGetX(Dist) > 0) + return; // pass through back faces + + // If the center of the sphere is farther from the plane of the triangle than + // the radius of the sphere, then there cannot be an intersection. + XMVECTOR NoIntersection = XMVectorLess(Dist, XMVectorNegate(Radius)); + NoIntersection = XMVectorOrInt(NoIntersection, XMVectorGreater(Dist, Radius)); + + // Project the center of the sphere onto the plane of the triangle. + XMVECTOR Point0 = XMVectorNegativeMultiplySubtract(N, Dist, Center); + + // Is it inside all the edges? If so we intersect because the distance + // to the plane is less than the radius. + //XMVECTOR Intersection = DirectX::Internal::PointOnPlaneInsideTriangle(Point0, p0, p1, p2); + + // Compute the cross products of the vector from the base of each edge to + // the point with each edge vector. + XMVECTOR C0 = XMVector3Cross(XMVectorSubtract(Point0, p0), XMVectorSubtract(p1, p0)); + XMVECTOR C1 = XMVector3Cross(XMVectorSubtract(Point0, p1), XMVectorSubtract(p2, p1)); + XMVECTOR C2 = XMVector3Cross(XMVectorSubtract(Point0, p2), XMVectorSubtract(p0, p2)); + + // If the cross product points in the same direction as the normal the the + // point is inside the edge (it is zero if is on the edge). + XMVECTOR Zero = XMVectorZero(); + XMVECTOR Inside0 = XMVectorLessOrEqual(XMVector3Dot(C0, N), Zero); + XMVECTOR Inside1 = XMVectorLessOrEqual(XMVector3Dot(C1, N), Zero); + XMVECTOR Inside2 = XMVectorLessOrEqual(XMVector3Dot(C2, N), Zero); + + // If the point inside all of the edges it is inside. + XMVECTOR Intersection = XMVectorAndInt(XMVectorAndInt(Inside0, Inside1), Inside2); + + bool inside = XMVector4EqualInt(XMVectorAndCInt(Intersection, NoIntersection), XMVectorTrueInt()); + + // Find the nearest point on each edge. + + // Edge 0,1 + XMVECTOR Point1 = wi::math::ClosestPointOnLineSegment(p0, p1, Center); + + // If the distance to the center of the sphere to the point is less than + // the radius of the sphere then it must intersect. + Intersection = XMVectorOrInt(Intersection, XMVectorLessOrEqual(XMVector3LengthSq(XMVectorSubtract(Center, Point1)), RadiusSq)); + + // Edge 1,2 + XMVECTOR Point2 = wi::math::ClosestPointOnLineSegment(p1, p2, Center); + + // If the distance to the center of the sphere to the point is less than + // the radius of the sphere then it must intersect. + Intersection = XMVectorOrInt(Intersection, XMVectorLessOrEqual(XMVector3LengthSq(XMVectorSubtract(Center, Point2)), RadiusSq)); + + // Edge 2,0 + XMVECTOR Point3 = wi::math::ClosestPointOnLineSegment(p2, p0, Center); + + // If the distance to the center of the sphere to the point is less than + // the radius of the sphere then it must intersect. + Intersection = XMVectorOrInt(Intersection, XMVectorLessOrEqual(XMVector3LengthSq(XMVectorSubtract(Center, Point3)), RadiusSq)); + + bool intersects = XMVector4EqualInt(XMVectorAndCInt(Intersection, NoIntersection), XMVectorTrueInt()); + + if (intersects) + { + XMVECTOR bestPoint = Point0; + if (!inside) + { + // If the sphere center's projection on the triangle plane is not within the triangle, + // determine the closest point on triangle to the sphere center + float bestDist = XMVectorGetX(XMVector3LengthSq(Point1 - Center)); + bestPoint = Point1; + + float d = XMVectorGetX(XMVector3LengthSq(Point2 - Center)); + if (d < bestDist) + { + bestDist = d; + bestPoint = Point2; + } + d = XMVectorGetX(XMVector3LengthSq(Point3 - Center)); + if (d < bestDist) + { + bestDist = d; + bestPoint = Point3; + } + } + XMVECTOR intersectionVec = Center - bestPoint; + XMVECTOR intersectionVecLen = XMVector3Length(intersectionVec); + + float depth = capsule.radius - XMVectorGetX(intersectionVecLen); + if (depth > result.depth) + { + result.entity = entity; + result.depth = depth; + XMStoreFloat3(&result.position, bestPoint); + XMStoreFloat3(&result.normal, intersectionVec / intersectionVecLen); + + XMVECTOR vel = bestPoint - XMVector3Transform(XMVector3Transform(bestPoint, objectMat_Inverse), objectMatPrev); + XMStoreFloat3(&result.velocity, vel); + } + } + }; + + if (mesh->bvh.IsValid()) + { + XMFLOAT3 base_local; + XMFLOAT3 tip_local; + float radius_local; + XMStoreFloat3(&base_local, XMVector3Transform(XMLoadFloat3(&capsule.base), objectMat_Inverse)); + XMStoreFloat3(&tip_local, XMVector3Transform(XMLoadFloat3(&capsule.tip), objectMat_Inverse)); + XMStoreFloat(&radius_local, XMVector3TransformNormal(XMLoadFloat(&capsule.radius), objectMat_Inverse)); + AABB capsule_local_aabb = Capsule(base_local, tip_local, radius_local).getAABB(); + + mesh->bvh.Intersects(capsule_local_aabb, 0, [&](uint32_t index){ + const uint32_t userdata = mesh->bvh_leaf_aabbs[index].userdata; + const uint32_t triangleIndex = userdata & 0xFFFFFF; + const uint32_t subsetIndex = userdata >> 24u; + const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex]; + if (subset.indexCount == 0) + return; + const uint32_t indexOffset = subset.indexOffset; + intersect_triangle(subsetIndex, indexOffset, triangleIndex); }); } + else + { + // Brute-force interection test: + uint32_t first_subset = 0; + uint32_t last_subset = 0; + mesh->GetLODSubsetRange(lod, first_subset, last_subset); + for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex) + { + const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex]; + if (subset.indexCount == 0) + continue; + const uint32_t indexOffset = subset.indexOffset; + const uint32_t triangleCount = subset.indexCount / 3; - } - } + for (uint32_t triangleIndex = 0; triangleIndex < triangleCount; ++triangleIndex) + { + intersect_triangle(subsetIndex, indexOffset, triangleIndex); + } + } + } - // Merge thread results: - wi::jobsystem::Wait(ctx); - CapsuleIntersectionResult& result = jobDataFunction.groupResults[0]; - for (uint32_t t = 1; t < threadCount; ++t) - { - if (jobDataFunction.groupResults[t].depth > result.depth) - { - result = jobDataFunction.groupResults[t]; } } diff --git a/WickedEngine/wiScene.h b/WickedEngine/wiScene.h index e4ea3ea79..a382314fc 100644 --- a/WickedEngine/wiScene.h +++ b/WickedEngine/wiScene.h @@ -254,7 +254,7 @@ namespace wi::scene wi::primitive::AABB* aabb_colliders_cpu = nullptr; ColliderComponent* colliders_cpu = nullptr; ColliderComponent* colliders_gpu = nullptr; - wi::BVH spring_collider_bvh; + wi::BVH collider_bvh; // Ocean GPU state: wi::Ocean ocean; diff --git a/WickedEngine/wiScene_Components.cpp b/WickedEngine/wiScene_Components.cpp index be7e82737..e881ef97e 100644 --- a/WickedEngine/wiScene_Components.cpp +++ b/WickedEngine/wiScene_Components.cpp @@ -928,6 +928,36 @@ namespace wi::scene device->SetName(&BLASes[lod], std::string("MeshComponent::BLAS[LOD" + std::to_string(lod) + "]").c_str()); } } + void MeshComponent::BuildBVH() + { + bvh_leaf_aabbs.clear(); + uint32_t first_subset = 0; + uint32_t last_subset = 0; + GetLODSubsetRange(0, first_subset, last_subset); + for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex) + { + assert(subsetIndex <= 0xFF); // must fit into 8 bits userdata packing + const MeshComponent::MeshSubset& subset = subsets[subsetIndex]; + if (subset.indexCount == 0) + continue; + const uint32_t indexOffset = subset.indexOffset; + const uint32_t triangleCount = subset.indexCount / 3; + for (uint32_t triangleIndex = 0; triangleIndex < triangleCount; ++triangleIndex) + { + assert(triangleIndex <= 0xFFFFFF); // must fit into 24 bits userdata packing + const uint32_t i0 = indices[indexOffset + triangleIndex * 3 + 0]; + const uint32_t i1 = indices[indexOffset + triangleIndex * 3 + 1]; + const uint32_t i2 = indices[indexOffset + triangleIndex * 3 + 2]; + const XMFLOAT3& p0 = vertex_positions[i0]; + const XMFLOAT3& p1 = vertex_positions[i1]; + const XMFLOAT3& p2 = vertex_positions[i2]; + AABB aabb = wi::primitive::AABB(wi::math::Min(p0, wi::math::Min(p1, p2)), wi::math::Max(p0, wi::math::Max(p1, p2))); + aabb.userdata = (triangleIndex & 0xFFFFFF) | ((subsetIndex & 0xFF) << 24u); + bvh_leaf_aabbs.push_back(aabb); + } + } + bvh.Build(bvh_leaf_aabbs.data(), (uint32_t)bvh_leaf_aabbs.size()); + } void MeshComponent::ComputeNormals(COMPUTE_NORMALS compute) { // Start recalculating normals: @@ -1300,6 +1330,44 @@ namespace wi::scene sphere.radius = aabb.getRadius(); return sphere; } + size_t MeshComponent::GetMemoryUsageCPU() const + { + size_t size = + vertex_positions.size() * sizeof(XMFLOAT3) + + vertex_normals.size() * sizeof(XMFLOAT3) + + vertex_tangents.size() * sizeof(XMFLOAT4) + + vertex_uvset_0.size() * sizeof(XMFLOAT2) + + vertex_uvset_1.size() * sizeof(XMFLOAT2) + + vertex_boneindices.size() * sizeof(XMUINT4) + + vertex_boneweights.size() * sizeof(XMFLOAT4) + + vertex_atlas.size() * sizeof(XMFLOAT2) + + vertex_colors.size() * sizeof(uint32_t) + + vertex_windweights.size() * sizeof(uint8_t) + + indices.size() * sizeof(uint32_t); + + for (const MorphTarget& morph : morph_targets) + { + size += + morph.vertex_positions.size() * sizeof(XMFLOAT3) + + morph.vertex_normals.size() * sizeof(XMFLOAT3) + + morph.sparse_indices_positions.size() * sizeof(uint32_t) + + morph.sparse_indices_normals.size() * sizeof(uint32_t); + } + + size += GetMemoryUsageBVH(); + + return size; + } + size_t MeshComponent::GetMemoryUsageGPU() const + { + return generalBuffer.desc.size + streamoutBuffer.desc.size; + } + size_t MeshComponent::GetMemoryUsageBVH() const + { + return + bvh.allocation.capacity() + + bvh_leaf_aabbs.size() * sizeof(wi::primitive::AABB); + } void ObjectComponent::ClearLightmap() { diff --git a/WickedEngine/wiScene_Components.h b/WickedEngine/wiScene_Components.h index 9e329bb0e..8da93c137 100644 --- a/WickedEngine/wiScene_Components.h +++ b/WickedEngine/wiScene_Components.h @@ -13,6 +13,7 @@ #include "wiArchive.h" #include "wiRectPacker.h" #include "wiUnorderedSet.h" +#include "wiBVH.h" namespace wi::scene { @@ -337,6 +338,7 @@ namespace wi::scene _DEPRECATED_DIRTY_BINDLESS = 1 << 5, TLAS_FORCE_DOUBLE_SIDED = 1 << 6, DOUBLE_SIDED_SHADOW = 1 << 7, + BVH_ENABLED = 1 << 8, }; uint32_t _flags = RENDERABLE; @@ -424,15 +426,24 @@ namespace wi::scene }; mutable BLAS_STATE BLAS_state = BLAS_STATE_NEEDS_REBUILD; + wi::vector bvh_leaf_aabbs; + wi::BVH bvh; + inline void SetRenderable(bool value) { if (value) { _flags |= RENDERABLE; } else { _flags &= ~RENDERABLE; } } inline void SetDoubleSided(bool value) { if (value) { _flags |= DOUBLE_SIDED; } else { _flags &= ~DOUBLE_SIDED; } } inline void SetDoubleSidedShadow(bool value) { if (value) { _flags |= DOUBLE_SIDED_SHADOW; } else { _flags &= ~DOUBLE_SIDED_SHADOW; } } inline void SetDynamic(bool value) { if (value) { _flags |= DYNAMIC; } else { _flags &= ~DYNAMIC; } } + // Enable disable CPU-side BVH acceleration structure + // true: BVH will be built immediately if it doesn't exist yet + // false: BVH will be deleted immediately if it exists + inline void SetBVHEnabled(bool value) { if (value) { _flags |= BVH_ENABLED; if (!bvh.IsValid()) { BuildBVH(); } } else { _flags &= ~BVH_ENABLED; bvh = {}; bvh_leaf_aabbs.clear(); } } + inline bool IsRenderable() const { return _flags & RENDERABLE; } inline bool IsDoubleSided() const { return _flags & DOUBLE_SIDED; } inline bool IsDoubleSidedShadow() const { return _flags & DOUBLE_SIDED_SHADOW; } inline bool IsDynamic() const { return _flags & DYNAMIC; } + inline bool IsBVHEnabled() const { return _flags & BVH_ENABLED; } inline float GetTessellationFactor() const { return tessellationFactor; } inline wi::graphics::IndexBufferFormat GetIndexFormat() const { return wi::graphics::GetIndexBufferFormat((uint32_t)vertex_positions.size()); } @@ -459,6 +470,13 @@ namespace wi::scene void CreateStreamoutRenderData(); void CreateRaytracingRenderData(); + // Rebuilds CPU-side BVH acceleration structure + void BuildBVH(); + + size_t GetMemoryUsageCPU() const; + size_t GetMemoryUsageGPU() const; + size_t GetMemoryUsageBVH() const; + enum COMPUTE_NORMALS { COMPUTE_NORMALS_HARD, // hard face normals, can result in additional vertices generated diff --git a/WickedEngine/wiScene_Serializers.cpp b/WickedEngine/wiScene_Serializers.cpp index 863ef143e..12aa374b8 100644 --- a/WickedEngine/wiScene_Serializers.cpp +++ b/WickedEngine/wiScene_Serializers.cpp @@ -453,6 +453,11 @@ namespace wi::scene wi::jobsystem::Execute(seri.ctx, [&](wi::jobsystem::JobArgs args) { CreateRenderData(); + + if (IsBVHEnabled()) + { + BuildBVH(); + } }); } else diff --git a/WickedEngine/wiTerrain.cpp b/WickedEngine/wiTerrain.cpp index f5d72696d..fd8422ba3 100644 --- a/WickedEngine/wiTerrain.cpp +++ b/WickedEngine/wiTerrain.cpp @@ -588,6 +588,10 @@ namespace wi::terrain { chunk_mesh->tessellationFactor = 0; } + if (!chunk_mesh->bvh.IsValid()) + { + chunk_mesh->SetBVHEnabled(true); + } #if 0 // Test: remove off screen chunks @@ -850,7 +854,8 @@ namespace wi::terrain chunk_data.sphere.center.y += chunk_data.position.y; chunk_data.sphere.center.z += chunk_data.position.z; chunk_data.sphere.radius = mesh.aabb.getRadius(); - }); + mesh.SetBVHEnabled(true); + }); // If there were any vertices in this chunk that could be valid for grass, store the grass particle system: if (grass_valid_vertex_count.load() > 0) diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index 97b3755a4..361bb7c45 100644 --- a/WickedEngine/wiVersion.cpp +++ b/WickedEngine/wiVersion.cpp @@ -9,7 +9,7 @@ namespace wi::version // minor features, major updates, breaking compatibility changes const int minor = 71; // minor bug fixes, alterations, refactors, updates - const int revision = 235; + const int revision = 236; const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision); @@ -130,6 +130,7 @@ Patreon supporters - SAS_Controller - Dominik Madarász - Segfault +- Mike amanfo )"; return credits;