Files
WickedEngine/WickedEngine/wiScene.cpp
T
Turánszki János 1c38d93304 Editor gui v2 (#843)
2024-05-17 08:04:05 +02:00

6913 lines
246 KiB
C++

#include "wiScene.h"
#include "wiTextureHelper.h"
#include "wiResourceManager.h"
#include "wiPhysics.h"
#include "wiRenderer.h"
#include "wiJobSystem.h"
#include "wiSpinLock.h"
#include "wiHelper.h"
#include "wiRenderer.h"
#include "wiBacklog.h"
#include "wiTimer.h"
#include "wiUnorderedMap.h"
#include "wiLua.h"
#include "wiAllocator.h"
#include "wiProfiler.h"
#include "shaders/ShaderInterop_SurfelGI.h"
#include "shaders/ShaderInterop_DDGI.h"
using namespace wi::ecs;
using namespace wi::enums;
using namespace wi::graphics;
using namespace wi::primitive;
namespace wi::scene
{
const uint32_t small_subtask_groupsize = 64u;
void Scene::Update(float dt)
{
this->dt = dt;
time += dt;
wi::jobsystem::context ctx;
// Script system runs first, because it could create new entities and components
// So GPU persistent resources need to be created accordingly for them too:
RunScriptUpdateSystem(ctx);
ScanAnimationDependencies();
ScanSpringDependencies();
// Terrains updates kick off:
if (dt > 0)
{
// Because this also spawns render tasks, this must not be during dt == 0 (eg. background loading)
for (size_t i = 0; i < terrains.GetCount(); ++i)
{
wi::terrain::Terrain& terrain = terrains[i];
terrain.terrainEntity = terrains.GetEntity(i);
terrain.scene = this;
terrain.Generation_Update(camera);
}
}
GraphicsDevice* device = wi::graphics::GetDevice();
instanceArraySize = objects.GetCount() + hairs.GetCount() + emitters.GetCount();
if (impostors.GetCount() > 0)
{
impostorInstanceOffset = uint32_t(instanceArraySize);
instanceArraySize += 1;
}
if (weathers.GetCount() > 0 && weathers[0].rain_amount > 0)
{
rainInstanceOffset = uint32_t(instanceArraySize);
instanceArraySize += 1;
}
if (instanceUploadBuffer[0].desc.size < (instanceArraySize * sizeof(ShaderMeshInstance)))
{
GPUBufferDesc desc;
desc.stride = sizeof(ShaderMeshInstance);
desc.size = desc.stride * instanceArraySize * 2; // *2 to grow fast
desc.bind_flags = BindFlag::SHADER_RESOURCE;
desc.misc_flags = ResourceMiscFlag::BUFFER_STRUCTURED;
if (!device->CheckCapability(GraphicsDeviceCapability::CACHE_COHERENT_UMA))
{
// Non-UMA: separate Default usage buffer
device->CreateBuffer(&desc, nullptr, &instanceBuffer);
device->SetName(&instanceBuffer, "Scene::instanceBuffer");
// Upload buffer shouldn't be used by shaders with Non-UMA:
desc.bind_flags = BindFlag::NONE;
desc.misc_flags = ResourceMiscFlag::NONE;
}
desc.usage = Usage::UPLOAD;
for (int i = 0; i < arraysize(instanceUploadBuffer); ++i)
{
device->CreateBuffer(&desc, nullptr, &instanceUploadBuffer[i]);
device->SetName(&instanceUploadBuffer[i], "Scene::instanceUploadBuffer");
}
}
instanceArrayMapped = (ShaderMeshInstance*)instanceUploadBuffer[device->GetBufferIndex()].mapped_data;
materialArraySize = materials.GetCount();
if (impostors.GetCount() > 0)
{
impostorMaterialOffset = uint32_t(materialArraySize);
materialArraySize += 1;
}
if (weathers.GetCount() > 0 && weathers[0].rain_amount > 0)
{
rainMaterialOffset = uint32_t(materialArraySize);
materialArraySize += 1;
}
if (materialUploadBuffer[0].desc.size < (materialArraySize * sizeof(ShaderMaterial)))
{
GPUBufferDesc desc;
desc.stride = sizeof(ShaderMaterial);
desc.size = desc.stride * materialArraySize * 2; // *2 to grow fast
desc.bind_flags = BindFlag::SHADER_RESOURCE;
desc.misc_flags = ResourceMiscFlag::BUFFER_STRUCTURED;
if (!device->CheckCapability(GraphicsDeviceCapability::CACHE_COHERENT_UMA))
{
// Non-UMA: separate Default usage buffer
device->CreateBuffer(&desc, nullptr, &materialBuffer);
device->SetName(&materialBuffer, "Scene::materialBuffer");
// Upload buffer shouldn't be used by shaders with Non-UMA:
desc.bind_flags = BindFlag::NONE;
desc.misc_flags = ResourceMiscFlag::NONE;
}
desc.usage = Usage::UPLOAD;
for (int i = 0; i < arraysize(materialUploadBuffer); ++i)
{
device->CreateBuffer(&desc, nullptr, &materialUploadBuffer[i]);
device->SetName(&materialUploadBuffer[i], "Scene::materialUploadBuffer");
}
}
materialArrayMapped = (ShaderMaterial*)materialUploadBuffer[device->GetBufferIndex()].mapped_data;
// Occlusion culling read:
if(wi::renderer::GetOcclusionCullingEnabled() && !wi::renderer::GetFreezeCullingCameraEnabled())
{
uint32_t minQueryCount = uint32_t(objects.GetCount() + lights.GetCount() + 1); // +1: ocean (don't know for sure if it exists yet before weather update)
if (queryHeap.desc.query_count < minQueryCount)
{
GPUQueryHeapDesc desc;
desc.type = GpuQueryType::OCCLUSION_BINARY;
desc.query_count = minQueryCount * 2; // *2 to grow fast
bool success = device->CreateQueryHeap(&desc, &queryHeap);
assert(success);
GPUBufferDesc bd;
bd.usage = Usage::READBACK;
bd.size = desc.query_count * sizeof(uint64_t);
for (int i = 0; i < arraysize(queryResultBuffer); ++i)
{
success = device->CreateBuffer(&bd, nullptr, &queryResultBuffer[i]);
assert(success);
device->SetName(&queryResultBuffer[i], "Scene::queryResultBuffer");
}
if (device->CheckCapability(GraphicsDeviceCapability::PREDICATION))
{
bd.usage = Usage::DEFAULT;
bd.misc_flags |= ResourceMiscFlag::PREDICATION;
success = device->CreateBuffer(&bd, nullptr, &queryPredicationBuffer);
assert(success);
device->SetName(&queryPredicationBuffer, "Scene::queryPredicationBuffer");
}
}
// Advance to next query result buffer to use (this will be the oldest one that was written)
queryheap_idx = device->GetBufferIndex();
// Clear query allocation state:
queryAllocator.store(0);
}
if (dt > 0)
{
// Scan objects to check if lightmap rendering is requested:
lightmap_request_allocator.store(0);
lightmap_requests.reserve(objects.GetCount());
wi::jobsystem::Dispatch(ctx, (uint32_t)objects.GetCount(), small_subtask_groupsize, [this](wi::jobsystem::JobArgs args) {
ObjectComponent& object = objects[args.jobIndex];
if (object.IsLightmapRenderRequested())
{
uint32_t request_index = lightmap_request_allocator.fetch_add(1);
*(lightmap_requests.data() + request_index) = args.jobIndex;
}
});
// Scan mesh subset counts and skinning data sizes to allocate GPU geometry data:
geometryAllocator.store(0u);
skinningAllocator.store(0u);
wi::jobsystem::Dispatch(ctx, (uint32_t)meshes.GetCount(), small_subtask_groupsize, [&](wi::jobsystem::JobArgs args) {
MeshComponent& mesh = meshes[args.jobIndex];
mesh.geometryOffset = geometryAllocator.fetch_add((uint32_t)mesh.subsets.size());
skinningAllocator.fetch_add(uint32_t(mesh.morph_targets.size() * sizeof(MorphTargetGPU)));
});
wi::jobsystem::Dispatch(ctx, (uint32_t)armatures.GetCount(), small_subtask_groupsize, [&](wi::jobsystem::JobArgs args) {
ArmatureComponent& armature = armatures[args.jobIndex];
skinningAllocator.fetch_add(uint32_t(armature.boneCollection.size() * sizeof(ShaderTransform)));
});
wi::jobsystem::Execute(ctx, [&](wi::jobsystem::JobArgs args) {
// Must not keep inactive instances, so init them for safety:
ShaderMeshInstance inst;
inst.init();
for (uint32_t i = 0; i < instanceArraySize; ++i)
{
std::memcpy(instanceArrayMapped + i, &inst, sizeof(inst));
}
});
}
RunAnimationUpdateSystem(ctx);
wi::physics::RunPhysicsUpdateSystem(ctx, *this, dt);
RunTransformUpdateSystem(ctx);
wi::jobsystem::Wait(ctx); // dependencies
RunHierarchyUpdateSystem(ctx);
// Lightmap requests are determined at this point, so we know if we need TLAS or not:
if (lightmap_request_allocator.load() > 0)
{
SetAccelerationStructureUpdateRequested(true);
}
// This must be after lightmap requests were determined:
TLAS_instancesMapped = nullptr;
if (IsAccelerationStructureUpdateRequested() && device->CheckCapability(GraphicsDeviceCapability::RAYTRACING))
{
GPUBufferDesc desc;
desc.stride = (uint32_t)device->GetTopLevelAccelerationStructureInstanceSize();
desc.size = desc.stride * instanceArraySize * 2; // *2 to grow fast
desc.usage = Usage::UPLOAD;
if (TLAS_instancesUpload->desc.size < desc.size)
{
for (int i = 0; i < arraysize(TLAS_instancesUpload); ++i)
{
device->CreateBuffer(&desc, nullptr, &TLAS_instancesUpload[i]);
device->SetName(&TLAS_instancesUpload[i], "Scene::TLAS_instancesUpload");
}
}
TLAS_instancesMapped = TLAS_instancesUpload[device->GetBufferIndex()].mapped_data;
wi::jobsystem::Execute(ctx, [&](wi::jobsystem::JobArgs args) {
// Must not keep inactive TLAS instances, so zero them out for safety:
std::memset(TLAS_instancesMapped, 0, TLAS_instancesUpload->desc.size);
});
}
// GPU subset count allocation is ready at this point:
geometryArraySize = geometryAllocator.load();
geometryArraySize += hairs.GetCount();
geometryArraySize += emitters.GetCount();
if (impostors.GetCount() > 0)
{
impostorGeometryOffset = uint32_t(geometryArraySize);
geometryArraySize += 1;
}
if (weathers.GetCount() > 0 && weathers[0].rain_amount > 0)
{
rainGeometryOffset = uint32_t(geometryArraySize);
geometryArraySize += 1;
}
if (geometryUploadBuffer[0].desc.size < (geometryArraySize * sizeof(ShaderGeometry)))
{
GPUBufferDesc desc;
desc.stride = sizeof(ShaderGeometry);
desc.size = desc.stride * geometryArraySize * 2; // *2 to grow fast
desc.bind_flags = BindFlag::SHADER_RESOURCE;
desc.misc_flags = ResourceMiscFlag::BUFFER_STRUCTURED;
if (!device->CheckCapability(GraphicsDeviceCapability::CACHE_COHERENT_UMA))
{
// Non-UMA: separate Default usage buffer
device->CreateBuffer(&desc, nullptr, &geometryBuffer);
device->SetName(&geometryBuffer, "Scene::geometryBuffer");
// Upload buffer shouldn't be used by shaders with Non-UMA:
desc.bind_flags = BindFlag::NONE;
desc.misc_flags = ResourceMiscFlag::NONE;
}
desc.usage = Usage::UPLOAD;
for (int i = 0; i < arraysize(geometryUploadBuffer); ++i)
{
device->CreateBuffer(&desc, nullptr, &geometryUploadBuffer[i]);
device->SetName(&geometryUploadBuffer[i], "Scene::geometryUploadBuffer");
}
}
geometryArrayMapped = (ShaderGeometry*)geometryUploadBuffer[device->GetBufferIndex()].mapped_data;
// Skinning data size is ready at this point:
skinningDataSize = skinningAllocator.load();
skinningAllocator.store(0);
if (skinningUploadBuffer[0].desc.size < skinningDataSize)
{
GPUBufferDesc desc;
desc.size = skinningDataSize * 2; // *2 to grow fast
desc.bind_flags = BindFlag::SHADER_RESOURCE;
desc.misc_flags = ResourceMiscFlag::BUFFER_RAW;
if (!device->CheckCapability(GraphicsDeviceCapability::CACHE_COHERENT_UMA))
{
// Non-UMA: separate Default usage buffer
device->CreateBuffer(&desc, nullptr, &skinningBuffer);
device->SetName(&skinningBuffer, "Scene::skinningBuffer");
// Upload buffer shouldn't be used by shaders with Non-UMA:
desc.bind_flags = BindFlag::NONE;
desc.misc_flags = ResourceMiscFlag::NONE;
}
desc.usage = Usage::UPLOAD;
for (int i = 0; i < arraysize(skinningUploadBuffer); ++i)
{
device->CreateBuffer(&desc, nullptr, &skinningUploadBuffer[i]);
device->SetName(&skinningUploadBuffer[i], "Scene::skinningUploadBuffer");
}
}
skinningDataMapped = skinningUploadBuffer[device->GetBufferIndex()].mapped_data;
RunExpressionUpdateSystem(ctx);
RunMeshUpdateSystem(ctx);
RunMaterialUpdateSystem(ctx);
wi::jobsystem::Wait(ctx); // dependencies
RunProceduralAnimationUpdateSystem(ctx);
RunArmatureUpdateSystem(ctx);
RunWeatherUpdateSystem(ctx);
wi::jobsystem::Wait(ctx); // dependencies
RunObjectUpdateSystem(ctx);
RunCameraUpdateSystem(ctx);
RunDecalUpdateSystem(ctx);
RunProbeUpdateSystem(ctx);
RunForceUpdateSystem(ctx);
RunLightUpdateSystem(ctx);
RunParticleUpdateSystem(ctx);
RunSoundUpdateSystem(ctx);
RunVideoUpdateSystem(ctx);
RunImpostorUpdateSystem(ctx);
RunSpriteUpdateSystem(ctx);
RunFontUpdateSystem(ctx);
wi::jobsystem::Wait(ctx); // dependencies
// Merge parallel bounds computation (depends on object update system):
bounds = AABB();
for (auto& group_bound : parallel_bounds)
{
bounds = AABB::Merge(bounds, group_bound);
}
// Meshlet buffer:
uint32_t meshletCount = meshletAllocator.load();
if(meshletBuffer.desc.size < meshletCount * sizeof(ShaderMeshlet))
{
GPUBufferDesc desc;
desc.stride = sizeof(ShaderMeshlet);
desc.size = desc.stride * meshletCount * 2; // *2 to grow fast
desc.bind_flags = BindFlag::SHADER_RESOURCE | BindFlag::UNORDERED_ACCESS;
desc.misc_flags = ResourceMiscFlag::BUFFER_STRUCTURED;
bool success = device->CreateBuffer(&desc, nullptr, &meshletBuffer);
assert(success);
device->SetName(&meshletBuffer, "meshletBuffer");
}
if (IsAccelerationStructureUpdateRequested())
{
if (device->CheckCapability(GraphicsDeviceCapability::RAYTRACING))
{
// Recreate top level acceleration structure if the object count changed:
if (TLAS.desc.top_level.count < instanceArraySize)
{
RaytracingAccelerationStructureDesc desc;
desc.flags = RaytracingAccelerationStructureDesc::FLAG_PREFER_FAST_BUILD;
desc.type = RaytracingAccelerationStructureDesc::Type::TOPLEVEL;
desc.top_level.count = (uint32_t)instanceArraySize * 2; // *2 to grow fast
GPUBufferDesc bufdesc;
bufdesc.misc_flags |= ResourceMiscFlag::RAY_TRACING;
bufdesc.stride = (uint32_t)device->GetTopLevelAccelerationStructureInstanceSize();
bufdesc.size = bufdesc.stride * desc.top_level.count;
bool success = device->CreateBuffer(&bufdesc, nullptr, &desc.top_level.instance_buffer);
assert(success);
device->SetName(&desc.top_level.instance_buffer, "Scene::TLAS.instanceBuffer");
success = device->CreateRaytracingAccelerationStructure(&desc, &TLAS);
assert(success);
device->SetName(&TLAS, "Scene::TLAS");
}
}
else
{
// Software GPU BVH:
BVH.Update(*this);
}
}
// Update water ripples:
for (size_t i = 0; i < waterRipples.size(); ++i)
{
auto& ripple = waterRipples[i];
ripple.Update(dt * 60);
// Remove inactive ripples:
if (ripple.params.opacity <= 0 + FLT_EPSILON || ripple.params.fade >= 1 - FLT_EPSILON)
{
ripple = waterRipples.back();
waterRipples.pop_back();
i--;
}
}
if (wi::renderer::GetSurfelGIEnabled())
{
if (!surfelgi.surfelBuffer.IsValid())
{
surfelgi.cleared = false;
GPUBufferDesc buf;
buf.stride = sizeof(Surfel);
buf.size = buf.stride * SURFEL_CAPACITY;
buf.misc_flags = ResourceMiscFlag::BUFFER_STRUCTURED;
buf.bind_flags = BindFlag::SHADER_RESOURCE | BindFlag::UNORDERED_ACCESS;
device->CreateBuffer(&buf, nullptr, &surfelgi.surfelBuffer);
device->SetName(&surfelgi.surfelBuffer, "surfelgi.surfelBuffer");
buf.stride = sizeof(SurfelData);
buf.size = buf.stride * SURFEL_CAPACITY;
buf.misc_flags = ResourceMiscFlag::BUFFER_STRUCTURED;
device->CreateBuffer(&buf, nullptr, &surfelgi.dataBuffer);
device->SetName(&surfelgi.dataBuffer, "surfelgi.dataBuffer");
buf.stride = sizeof(SurfelVarianceDataPacked);
buf.size = buf.stride * SURFEL_CAPACITY * SURFEL_MOMENT_RESOLUTION * SURFEL_MOMENT_RESOLUTION;
buf.misc_flags = ResourceMiscFlag::BUFFER_STRUCTURED;
device->CreateBuffer(&buf, nullptr, &surfelgi.varianceBuffer);
device->SetName(&surfelgi.varianceBuffer, "surfelgi.varianceBuffer");
buf.stride = sizeof(uint);
buf.size = buf.stride * SURFEL_CAPACITY;
buf.misc_flags = ResourceMiscFlag::BUFFER_STRUCTURED;
device->CreateBuffer(&buf, nullptr, &surfelgi.aliveBuffer[0]);
device->SetName(&surfelgi.aliveBuffer[0], "surfelgi.aliveBuffer[0]");
device->CreateBuffer(&buf, nullptr, &surfelgi.aliveBuffer[1]);
device->SetName(&surfelgi.aliveBuffer[1], "surfelgi.aliveBuffer[1]");
auto fill_dead_indices = [&](void* dest) {
uint32_t* dead_indices = (uint32_t*)dest;
for (uint32_t i = 0; i < SURFEL_CAPACITY; ++i)
{
uint32_t ind = uint32_t(SURFEL_CAPACITY - 1 - i);
std::memcpy(dead_indices + i, &ind, sizeof(ind));
}
};
device->CreateBuffer2(&buf, fill_dead_indices, &surfelgi.deadBuffer);
device->SetName(&surfelgi.deadBuffer, "surfelgi.deadBuffer");
buf.stride = sizeof(uint);
buf.size = SURFEL_STATS_SIZE;
buf.misc_flags = ResourceMiscFlag::BUFFER_RAW;
uint stats_data[] = { 0,0,SURFEL_CAPACITY,0,0,0 };
device->CreateBuffer(&buf, &stats_data, &surfelgi.statsBuffer);
device->SetName(&surfelgi.statsBuffer, "surfelgi.statsBuffer");
buf.stride = sizeof(uint);
buf.size = SURFEL_INDIRECT_SIZE;
buf.misc_flags = ResourceMiscFlag::BUFFER_RAW | ResourceMiscFlag::INDIRECT_ARGS;
uint indirect_data[] = { 0,0,0, 0,0,0, 0,0,0 };
device->CreateBuffer(&buf, &indirect_data, &surfelgi.indirectBuffer);
device->SetName(&surfelgi.indirectBuffer, "surfelgi.indirectBuffer");
buf.stride = sizeof(SurfelGridCell);
buf.size = buf.stride * SURFEL_TABLE_SIZE;
buf.misc_flags = ResourceMiscFlag::BUFFER_STRUCTURED;
device->CreateBuffer(&buf, nullptr, &surfelgi.gridBuffer);
device->SetName(&surfelgi.gridBuffer, "surfelgi.gridBuffer");
buf.stride = sizeof(uint);
buf.size = buf.stride * SURFEL_CAPACITY * 27; // each surfel can be in 3x3x3=27 cells
buf.misc_flags = ResourceMiscFlag::BUFFER_STRUCTURED;
device->CreateBuffer(&buf, nullptr, &surfelgi.cellBuffer);
device->SetName(&surfelgi.cellBuffer, "surfelgi.cellBuffer");
buf.stride = sizeof(SurfelRayDataPacked);
buf.size = buf.stride * SURFEL_RAY_BUDGET;
buf.misc_flags = ResourceMiscFlag::BUFFER_STRUCTURED;
device->CreateBuffer(&buf, nullptr, &surfelgi.rayBuffer);
device->SetName(&surfelgi.rayBuffer, "surfelgi.rayBuffer");
TextureDesc tex;
tex.width = SURFEL_MOMENT_ATLAS_TEXELS;
tex.height = SURFEL_MOMENT_ATLAS_TEXELS;
tex.format = Format::R16G16_FLOAT;
tex.bind_flags = BindFlag::UNORDERED_ACCESS | BindFlag::SHADER_RESOURCE;
tex.layout = ResourceState::SHADER_RESOURCE_COMPUTE;
device->CreateTexture(&tex, nullptr, &surfelgi.momentsTexture);
device->SetName(&surfelgi.momentsTexture, "surfelgi.momentsTexture");
tex.bind_flags = BindFlag::SHADER_RESOURCE;
tex.misc_flags = ResourceMiscFlag::SPARSE;
tex.format = Format::BC6H_UF16;
tex.width = SURFEL_MOMENT_ATLAS_TEXELS;
tex.height = SURFEL_MOMENT_ATLAS_TEXELS;
tex.width = std::max(256u, tex.width); // force non-packed mip behaviour
tex.height = std::max(256u, tex.height); // force non-packed mip behaviour
device->CreateTexture(&tex, nullptr, &surfelgi.irradianceTexture);
device->SetName(&surfelgi.irradianceTexture, "surfelgi.irradianceTexture");
tex.bind_flags = BindFlag::UNORDERED_ACCESS | BindFlag::SHADER_RESOURCE;
tex.misc_flags = ResourceMiscFlag::SPARSE;
tex.width = SURFEL_MOMENT_ATLAS_TEXELS / 4;
tex.height = SURFEL_MOMENT_ATLAS_TEXELS / 4;
tex.format = Format::R32G32B32A32_UINT;
tex.layout = ResourceState::UNORDERED_ACCESS;
device->CreateTexture(&tex, nullptr, &surfelgi.irradianceTexture_rw);
device->SetName(&surfelgi.irradianceTexture_rw, "surfelgi.irradianceTexture_rw");
buf = {};
buf.alignment = surfelgi.irradianceTexture.sparse_page_size;
buf.size = surfelgi.irradianceTexture.sparse_properties->total_tile_count * buf.alignment * 2;
buf.misc_flags = ResourceMiscFlag::SPARSE_TILE_POOL_TEXTURE_NON_RT_DS;
device->CreateBuffer(&buf, nullptr, &surfelgi.sparse_tile_pool);
SparseUpdateCommand commands[2];
commands[0].sparse_resource = &surfelgi.irradianceTexture;
commands[0].tile_pool = &surfelgi.sparse_tile_pool;
commands[0].num_resource_regions = 1;
uint32_t tile_count = surfelgi.irradianceTexture_rw.sparse_properties->total_tile_count;
uint32_t tile_offset[2] = { 0, tile_count };
SparseRegionSize region;
region.width = (tex.width + surfelgi.irradianceTexture_rw.sparse_properties->tile_width - 1) / surfelgi.irradianceTexture_rw.sparse_properties->tile_width;
region.height = (tex.height + surfelgi.irradianceTexture_rw.sparse_properties->tile_height - 1) / surfelgi.irradianceTexture_rw.sparse_properties->tile_height;
SparseResourceCoordinate coordinate;
coordinate.x = 0;
coordinate.y = 0;
TileRangeFlags flags = TileRangeFlags::None;
commands[0].sizes = &region;
commands[0].coordinates = &coordinate;
commands[0].range_flags = &flags;
commands[0].range_tile_counts = &tile_count;
commands[0].range_start_offsets = &tile_offset[0];
commands[1] = commands[0];
commands[1].sparse_resource = &surfelgi.irradianceTexture_rw;
device->SparseUpdate(QUEUE_GRAPHICS, commands, arraysize(commands));
}
std::swap(surfelgi.aliveBuffer[0], surfelgi.aliveBuffer[1]);
}
else
{
surfelgi = {};
}
if (wi::renderer::GetDDGIEnabled())
{
ddgi.frame_index++;
if (!ddgi.color_texture_rw.IsValid()) // Check the _rw texture here because that is invalid with serialized DDGI data, and we can detect if dynamic resources need recreation when serialized is loaded
{
ddgi.frame_index = 0;
const uint32_t probe_count = ddgi.grid_dimensions.x * ddgi.grid_dimensions.y * ddgi.grid_dimensions.z;
GPUBufferDesc buf;
buf.stride = sizeof(DDGIRayDataPacked);
buf.size = buf.stride * probe_count * DDGI_MAX_RAYCOUNT;
buf.bind_flags = BindFlag::UNORDERED_ACCESS | BindFlag::SHADER_RESOURCE;
buf.misc_flags = ResourceMiscFlag::BUFFER_STRUCTURED;
device->CreateBuffer(&buf, nullptr, &ddgi.ray_buffer);
device->SetName(&ddgi.ray_buffer, "ddgi.ray_buffer");
buf.stride = sizeof(DDGIVarianceDataPacked);
buf.size = buf.stride * probe_count * DDGI_COLOR_RESOLUTION * DDGI_COLOR_RESOLUTION;
buf.misc_flags = ResourceMiscFlag::BUFFER_STRUCTURED;
device->CreateBuffer(&buf, nullptr, &ddgi.variance_buffer);
device->SetName(&ddgi.variance_buffer, "ddgi.variance_buffer");
buf.stride = sizeof(uint8_t);
buf.size = buf.stride * probe_count;
buf.misc_flags = ResourceMiscFlag::NONE;
buf.format = Format::R8_UINT;
device->CreateBuffer(&buf, nullptr, &ddgi.raycount_buffer);
device->SetName(&ddgi.raycount_buffer, "ddgi.raycount_buffer");
buf.stride = sizeof(uint32_t);
buf.size = buf.stride * (probe_count * DDGI_MAX_RAYCOUNT + 4); // +4: counter/indirect dispatch args
buf.misc_flags = ResourceMiscFlag::BUFFER_STRUCTURED;
buf.format = Format::UNKNOWN;
device->CreateBuffer(&buf, nullptr, &ddgi.rayallocation_buffer);
device->SetName(&ddgi.rayallocation_buffer, "ddgi.rayallocation_buffer");
TextureDesc tex;
tex.width = DDGI_COLOR_TEXELS * ddgi.grid_dimensions.x * ddgi.grid_dimensions.y;
tex.height = DDGI_COLOR_TEXELS * ddgi.grid_dimensions.z;
tex.format = Format::BC6H_UF16;
tex.misc_flags = ResourceMiscFlag::SPARSE; // sparse aliasing to write BC6H_UF16 as uint
tex.width = std::max(256u, tex.width); // force non-packed mip behaviour
tex.height = std::max(256u, tex.height); // force non-packed mip behaviour
tex.bind_flags = BindFlag::SHADER_RESOURCE;
tex.layout = ResourceState::SHADER_RESOURCE;
device->CreateTexture(&tex, nullptr, &ddgi.color_texture);
device->SetName(&ddgi.color_texture, "ddgi.color_texture");
tex.format = Format::R32G32B32A32_UINT; // packed BC6H_UF16
tex.width /= 4;
tex.height /= 4;
tex.bind_flags = BindFlag::UNORDERED_ACCESS;
tex.layout = ResourceState::UNORDERED_ACCESS;
device->CreateTexture(&tex, nullptr, &ddgi.color_texture_rw);
device->SetName(&ddgi.color_texture_rw, "ddgi.color_texture_rw");
buf = {};
buf.alignment = ddgi.color_texture_rw.sparse_page_size;
buf.size = ddgi.color_texture_rw.sparse_properties->total_tile_count * buf.alignment * 2;
buf.misc_flags = ResourceMiscFlag::SPARSE_TILE_POOL_TEXTURE_NON_RT_DS;
device->CreateBuffer(&buf, nullptr, &ddgi.sparse_tile_pool);
SparseUpdateCommand commands[2];
commands[0].sparse_resource = &ddgi.color_texture;
commands[0].tile_pool = &ddgi.sparse_tile_pool;
commands[0].num_resource_regions = 1;
uint32_t tile_count = ddgi.color_texture_rw.sparse_properties->total_tile_count;
uint32_t tile_offset[2] = { 0, tile_count };
SparseRegionSize region;
region.width = (tex.width + ddgi.color_texture_rw.sparse_properties->tile_width - 1) / ddgi.color_texture_rw.sparse_properties->tile_width;
region.height = (tex.height + ddgi.color_texture_rw.sparse_properties->tile_height - 1) / ddgi.color_texture_rw.sparse_properties->tile_height;
SparseResourceCoordinate coordinate;
coordinate.x = 0;
coordinate.y = 0;
TileRangeFlags flags = TileRangeFlags::None;
commands[0].sizes = &region;
commands[0].coordinates = &coordinate;
commands[0].range_flags = &flags;
commands[0].range_tile_counts = &tile_count;
commands[0].range_start_offsets = &tile_offset[0];
commands[1] = commands[0];
commands[1].sparse_resource = &ddgi.color_texture_rw;
device->SparseUpdate(QUEUE_GRAPHICS, commands, arraysize(commands));
tex.width = DDGI_DEPTH_TEXELS * ddgi.grid_dimensions.x * ddgi.grid_dimensions.y;
tex.height = DDGI_DEPTH_TEXELS * ddgi.grid_dimensions.z;
tex.format = Format::R16G16_FLOAT;
tex.misc_flags = {};
tex.bind_flags = BindFlag::UNORDERED_ACCESS | BindFlag::SHADER_RESOURCE;
tex.layout = ResourceState::SHADER_RESOURCE;
device->CreateTexture(&tex, nullptr, &ddgi.depth_texture);
device->SetName(&ddgi.depth_texture, "ddgi.depth_texture");
tex.type = TextureDesc::Type::TEXTURE_3D;
tex.width = ddgi.grid_dimensions.x;
tex.height = ddgi.grid_dimensions.z;
tex.depth = ddgi.grid_dimensions.y;
tex.format = Format::R10G10B10A2_UNORM;
tex.bind_flags = BindFlag::SHADER_RESOURCE | BindFlag::UNORDERED_ACCESS;
tex.layout = ResourceState::SHADER_RESOURCE_COMPUTE;
device->CreateTexture(&tex, nullptr, &ddgi.offset_texture);
device->SetName(&ddgi.offset_texture, "ddgi.offset_texture");
}
ddgi.grid_min = bounds.getMin();
ddgi.grid_min.x -= 1;
ddgi.grid_min.y -= 1;
ddgi.grid_min.z -= 1;
ddgi.grid_max = bounds.getMax();
ddgi.grid_max.x += 1;
ddgi.grid_max.y += 1;
ddgi.grid_max.z += 1;
}
else if (ddgi.color_texture_rw.IsValid()) // if color_texture_rw is valid, it means DDGI was not from serialization, so it will be deleted when DDGI is disabled
{
ddgi = {};
}
if (wi::renderer::GetVXGIEnabled())
{
if(!vxgi.radiance.IsValid())
{
TextureDesc desc;
desc.type = TextureDesc::Type::TEXTURE_3D;
desc.width = vxgi.res * (6 + DIFFUSE_CONE_COUNT);
desc.height = vxgi.res * VXGI_CLIPMAP_COUNT;
desc.depth = vxgi.res;
desc.mip_levels = 1;
desc.format = Format::R16G16B16A16_FLOAT;
desc.bind_flags = BindFlag::UNORDERED_ACCESS | BindFlag::SHADER_RESOURCE;
desc.usage = Usage::DEFAULT;
device->CreateTexture(&desc, nullptr, &vxgi.radiance);
device->SetName(&vxgi.radiance, "vxgi.radiance");
device->CreateTexture(&desc, nullptr, &vxgi.prev_radiance);
device->SetName(&vxgi.prev_radiance, "vxgi.prev_radiance");
vxgi.pre_clear = true;
}
if (!vxgi.render_atomic.IsValid())
{
TextureDesc desc;
desc.type = TextureDesc::Type::TEXTURE_3D;
desc.width = vxgi.res * 6;
desc.height = vxgi.res;
desc.depth = vxgi.res * VOXELIZATION_CHANNEL_COUNT;
desc.mip_levels = 1;
desc.usage = Usage::DEFAULT;
desc.bind_flags = BindFlag::UNORDERED_ACCESS | BindFlag::SHADER_RESOURCE;
desc.format = Format::R32_UINT;
device->CreateTexture(&desc, nullptr, &vxgi.render_atomic);
device->SetName(&vxgi.render_atomic, "vxgi.render_atomic");
}
if (!vxgi.sdf.IsValid())
{
TextureDesc desc;
desc.type = TextureDesc::Type::TEXTURE_3D;
desc.width = vxgi.res;
desc.height = vxgi.res * VXGI_CLIPMAP_COUNT;
desc.depth = vxgi.res;
desc.mip_levels = 1;
desc.usage = Usage::DEFAULT;
desc.bind_flags = BindFlag::UNORDERED_ACCESS | BindFlag::SHADER_RESOURCE;
desc.format = Format::R16_FLOAT;
device->CreateTexture(&desc, nullptr, &vxgi.sdf);
device->SetName(&vxgi.sdf, "vxgi.sdf");
device->CreateTexture(&desc, nullptr, &vxgi.sdf_temp);
device->SetName(&vxgi.sdf_temp, "vxgi.sdf_temp");
}
vxgi.clipmap_to_update = (vxgi.clipmap_to_update + 1) % VXGI_CLIPMAP_COUNT;
}
if (impostors.GetCount() > 0 && objects.GetCount() > 0)
{
impostor_ib_format = GetIndexBufferFormatRaw((uint32_t)objects.GetCount() * 4);
if (allocated_impostor_capacity < objects.GetCount())
{
allocated_impostor_capacity = uint32_t(objects.GetCount() * 2); // *2 to grow fast
GPUBufferDesc desc;
desc.usage = Usage::DEFAULT;
desc.bind_flags = BindFlag::INDEX_BUFFER | BindFlag::SHADER_RESOURCE | BindFlag::UNORDERED_ACCESS;
desc.misc_flags = ResourceMiscFlag::BUFFER_RAW | ResourceMiscFlag::TYPED_FORMAT_CASTING | ResourceMiscFlag::INDIRECT_ARGS | ResourceMiscFlag::NO_DEFAULT_DESCRIPTORS;
const uint64_t alignment =
device->GetMinOffsetAlignment(&desc) *
sizeof(IndirectDrawArgsIndexedInstanced) * // additional alignment
sizeof(MeshComponent::Vertex_POS32) // additional alignment
;
desc.size =
AlignTo(sizeof(IndirectDrawArgsIndexedInstanced), alignment) + // indirect args
AlignTo(allocated_impostor_capacity * sizeof(uint) * 6, alignment) + // indices (must overestimate here for 32-bit indices, because we create 16 bit and 32 bit descriptors)
AlignTo(allocated_impostor_capacity * sizeof(MeshComponent::Vertex_POS32) * 4, alignment) + // vertices
AlignTo(allocated_impostor_capacity * sizeof(MeshComponent::Vertex_NOR) * 4, alignment) + // vertices
AlignTo(allocated_impostor_capacity * sizeof(uint2), alignment) // impostordata
;
device->CreateBuffer(&desc, nullptr, &impostorBuffer);
device->SetName(&impostorBuffer, "impostorBuffer");
uint64_t buffer_offset = 0ull;
const uint32_t indirect_stride = sizeof(IndirectDrawArgsIndexedInstanced);
buffer_offset = AlignTo(buffer_offset, sizeof(IndirectDrawArgsIndexedInstanced)); // additional structured buffer alignment
buffer_offset = AlignTo(buffer_offset, alignment);
impostor_indirect.offset = buffer_offset;
impostor_indirect.size = sizeof(IndirectDrawArgsIndexedInstanced);
impostor_indirect.subresource_uav = device->CreateSubresource(&impostorBuffer, SubresourceType::UAV, impostor_indirect.offset, impostor_indirect.size, nullptr, &indirect_stride);
buffer_offset += impostor_indirect.size;
buffer_offset = AlignTo(buffer_offset, alignment);
Format format32 = Format::R32_UINT;
Format format16 = Format::R16_UINT;
impostor_ib32.offset = buffer_offset;
impostor_ib32.size = allocated_impostor_capacity * sizeof(uint32_t) * 6;
impostor_ib16.offset = buffer_offset;
impostor_ib16.size = allocated_impostor_capacity * sizeof(uint16_t) * 6;
impostor_ib32.subresource_srv = device->CreateSubresource(&impostorBuffer, SubresourceType::SRV, impostor_ib32.offset, impostor_ib32.size, &format32);
impostor_ib32.subresource_uav = device->CreateSubresource(&impostorBuffer, SubresourceType::UAV, impostor_ib32.offset, impostor_ib32.size, &format32);
impostor_ib32.descriptor_srv = device->GetDescriptorIndex(&impostorBuffer, SubresourceType::SRV, impostor_ib32.subresource_srv);
impostor_ib32.descriptor_uav = device->GetDescriptorIndex(&impostorBuffer, SubresourceType::UAV, impostor_ib32.subresource_uav);
buffer_offset += impostor_ib32.size;
impostor_ib16.subresource_srv = device->CreateSubresource(&impostorBuffer, SubresourceType::SRV, impostor_ib16.offset, impostor_ib16.size, &format16);
impostor_ib16.subresource_uav = device->CreateSubresource(&impostorBuffer, SubresourceType::UAV, impostor_ib16.offset, impostor_ib16.size, &format16);
impostor_ib16.descriptor_srv = device->GetDescriptorIndex(&impostorBuffer, SubresourceType::SRV, impostor_ib16.subresource_srv);
impostor_ib16.descriptor_uav = device->GetDescriptorIndex(&impostorBuffer, SubresourceType::UAV, impostor_ib16.subresource_uav);
buffer_offset = AlignTo(buffer_offset, alignment);
impostor_vb_pos.offset = buffer_offset;
impostor_vb_pos.size = allocated_impostor_capacity * sizeof(MeshComponent::Vertex_POS32) * 4;
impostor_vb_pos.subresource_srv = device->CreateSubresource(&impostorBuffer, SubresourceType::SRV, impostor_vb_pos.offset, impostor_vb_pos.size, &MeshComponent::Vertex_POS32::FORMAT);
impostor_vb_pos.subresource_uav = device->CreateSubresource(&impostorBuffer, SubresourceType::UAV, impostor_vb_pos.offset, impostor_vb_pos.size); // can't have RGB32F format for UAV!
impostor_vb_pos.descriptor_srv = device->GetDescriptorIndex(&impostorBuffer, SubresourceType::SRV, impostor_vb_pos.subresource_srv);
impostor_vb_pos.descriptor_uav = device->GetDescriptorIndex(&impostorBuffer, SubresourceType::UAV, impostor_vb_pos.subresource_uav);
buffer_offset += impostor_vb_pos.size;
buffer_offset = AlignTo(buffer_offset, alignment);
impostor_vb_nor.offset = buffer_offset;
impostor_vb_nor.size = allocated_impostor_capacity * sizeof(MeshComponent::Vertex_NOR) * 4;
impostor_vb_nor.subresource_srv = device->CreateSubresource(&impostorBuffer, SubresourceType::SRV, impostor_vb_nor.offset, impostor_vb_nor.size, &MeshComponent::Vertex_NOR::FORMAT);
impostor_vb_nor.subresource_uav = device->CreateSubresource(&impostorBuffer, SubresourceType::UAV, impostor_vb_nor.offset, impostor_vb_nor.size, &MeshComponent::Vertex_NOR::FORMAT);
impostor_vb_nor.descriptor_srv = device->GetDescriptorIndex(&impostorBuffer, SubresourceType::SRV, impostor_vb_nor.subresource_srv);
impostor_vb_nor.descriptor_uav = device->GetDescriptorIndex(&impostorBuffer, SubresourceType::UAV, impostor_vb_nor.subresource_uav);
buffer_offset += impostor_vb_nor.size;
buffer_offset = AlignTo(buffer_offset, alignment);
impostor_data.offset = buffer_offset;
impostor_data.size = allocated_impostor_capacity * sizeof(uint2);
impostor_data.subresource_srv = device->CreateSubresource(&impostorBuffer, SubresourceType::SRV, impostor_data.offset, impostor_data.size);
impostor_data.subresource_uav = device->CreateSubresource(&impostorBuffer, SubresourceType::UAV, impostor_data.offset, impostor_data.size);
impostor_data.descriptor_srv = device->GetDescriptorIndex(&impostorBuffer, SubresourceType::SRV, impostor_data.subresource_srv);
impostor_data.descriptor_uav = device->GetDescriptorIndex(&impostorBuffer, SubresourceType::UAV, impostor_data.subresource_uav);
buffer_offset += impostor_data.size;
}
}
// VXGI volume update:
// Note: this is using camera that the scene is associated with
{
VXGI::ClipMap& clipmap = vxgi.clipmaps[vxgi.clipmap_to_update];
clipmap.voxelsize = vxgi.clipmaps[0].voxelsize * (1u << vxgi.clipmap_to_update);
const float texelSize = clipmap.voxelsize * 2;
XMFLOAT3 center = XMFLOAT3(std::floor(camera.Eye.x / texelSize) * texelSize, std::floor(camera.Eye.y / texelSize) * texelSize, std::floor(camera.Eye.z / texelSize) * texelSize);
clipmap.offsetfromPrevFrame.x = int((clipmap.center.x - center.x) / texelSize);
clipmap.offsetfromPrevFrame.y = -int((clipmap.center.y - center.y) / texelSize);
clipmap.offsetfromPrevFrame.z = int((clipmap.center.z - center.z) / texelSize);
clipmap.center = center;
XMFLOAT3 extents = XMFLOAT3(vxgi.res * clipmap.voxelsize, vxgi.res * clipmap.voxelsize, vxgi.res * clipmap.voxelsize);
if (extents.x != clipmap.extents.x || extents.y != clipmap.extents.y || extents.z != clipmap.extents.z)
{
vxgi.pre_clear = true;
}
clipmap.extents = extents;
}
{
for (size_t voxelgridIndex = 0; voxelgridIndex < voxel_grids.GetCount(); ++voxelgridIndex)
{
wi::VoxelGrid& voxelgrid = voxel_grids[voxelgridIndex];
Entity entity = voxel_grids.GetEntity(voxelgridIndex);
const TransformComponent* transform = transforms.GetComponent(entity);
if (transform != nullptr)
{
voxelgrid.center = transform->GetPosition();
voxelgrid.set_voxelsize(transform->GetScale());
}
}
}
// Shader scene resources:
if (device->CheckCapability(GraphicsDeviceCapability::CACHE_COHERENT_UMA))
{
shaderscene.instancebuffer = device->GetDescriptorIndex(&instanceUploadBuffer[device->GetBufferIndex()], SubresourceType::SRV);
shaderscene.geometrybuffer = device->GetDescriptorIndex(&geometryUploadBuffer[device->GetBufferIndex()], SubresourceType::SRV);
shaderscene.materialbuffer = device->GetDescriptorIndex(&materialUploadBuffer[device->GetBufferIndex()], SubresourceType::SRV);
}
else
{
shaderscene.instancebuffer = device->GetDescriptorIndex(&instanceBuffer, SubresourceType::SRV);
shaderscene.geometrybuffer = device->GetDescriptorIndex(&geometryBuffer, SubresourceType::SRV);
shaderscene.materialbuffer = device->GetDescriptorIndex(&materialBuffer, SubresourceType::SRV);
}
shaderscene.meshletbuffer = device->GetDescriptorIndex(&meshletBuffer, SubresourceType::SRV);
if (weather.skyMap.IsValid())
{
shaderscene.globalenvmap = device->GetDescriptorIndex(&weather.skyMap.GetTexture(), SubresourceType::SRV, weather.skyMap.GetTextureSRGBSubresource());
}
else
{
shaderscene.globalenvmap = -1;
}
if (probes.GetCount() > 0 && probes[0].texture.IsValid())
{
shaderscene.globalprobe = device->GetDescriptorIndex(&probes[0].texture, SubresourceType::SRV);
}
else if (global_dynamic_probe.texture.IsValid())
{
shaderscene.globalprobe = device->GetDescriptorIndex(&global_dynamic_probe.texture, SubresourceType::SRV);
}
else
{
shaderscene.globalprobe = -1;
}
shaderscene.impostorInstanceOffset = impostorInstanceOffset;
shaderscene.TLAS = device->GetDescriptorIndex(&TLAS, SubresourceType::SRV);
shaderscene.BVH_counter = device->GetDescriptorIndex(&BVH.primitiveCounterBuffer, SubresourceType::SRV);
shaderscene.BVH_nodes = device->GetDescriptorIndex(&BVH.bvhNodeBuffer, SubresourceType::SRV);
shaderscene.BVH_primitives = device->GetDescriptorIndex(&BVH.primitiveBuffer, SubresourceType::SRV);
shaderscene.aabb_min = bounds.getMin();
shaderscene.aabb_max = bounds.getMax();
shaderscene.aabb_extents.x = abs(shaderscene.aabb_max.x - shaderscene.aabb_min.x);
shaderscene.aabb_extents.y = abs(shaderscene.aabb_max.y - shaderscene.aabb_min.y);
shaderscene.aabb_extents.z = abs(shaderscene.aabb_max.z - shaderscene.aabb_min.z);
shaderscene.aabb_extents_rcp.x = 1.0f / shaderscene.aabb_extents.x;
shaderscene.aabb_extents_rcp.y = 1.0f / shaderscene.aabb_extents.y;
shaderscene.aabb_extents_rcp.z = 1.0f / shaderscene.aabb_extents.z;
shaderscene.weather.sun_color = weather.sunColor;
shaderscene.weather.sun_direction = weather.sunDirection;
shaderscene.weather.most_important_light_index = weather.most_important_light_index;
shaderscene.weather.ambient = weather.ambient;
shaderscene.weather.sky_rotation_sin = std::sin(weather.sky_rotation);
shaderscene.weather.sky_rotation_cos = std::cos(weather.sky_rotation);
shaderscene.weather.fog.start = weather.fogStart;
shaderscene.weather.fog.density = weather.fogDensity;
shaderscene.weather.fog.height_start = weather.fogHeightStart;
shaderscene.weather.fog.height_end = weather.fogHeightEnd;
shaderscene.weather.horizon = weather.horizon;
shaderscene.weather.zenith = weather.zenith;
shaderscene.weather.sky_exposure = weather.skyExposure;
shaderscene.weather.wind.speed = weather.windSpeed;
shaderscene.weather.wind.randomness = weather.windRandomness;
shaderscene.weather.wind.wavesize = weather.windWaveSize;
shaderscene.weather.wind.direction = weather.windDirection;
shaderscene.weather.atmosphere = weather.atmosphereParameters;
shaderscene.weather.volumetric_clouds = weather.volumetricCloudParameters;
shaderscene.weather.ocean.water_color = weather.oceanParameters.waterColor;
shaderscene.weather.ocean.water_height = weather.oceanParameters.waterHeight;
shaderscene.weather.ocean.patch_size_rcp = 1.0f / weather.oceanParameters.patch_length;
shaderscene.weather.ocean.texture_displacementmap = device->GetDescriptorIndex(ocean.getDisplacementMap(), SubresourceType::SRV);
shaderscene.weather.ocean.texture_gradientmap = device->GetDescriptorIndex(ocean.getGradientMap(), SubresourceType::SRV);
shaderscene.weather.stars = weather.stars;
XMStoreFloat4x4(&shaderscene.weather.stars_rotation, XMMatrixRotationQuaternion(XMLoadFloat4(&weather.stars_rotation_quaternion)));
shaderscene.weather.rain_amount = weather.rain_amount;
shaderscene.weather.rain_length = weather.rain_length;
shaderscene.weather.rain_speed = weather.rain_speed;
shaderscene.weather.rain_scale = weather.rain_scale;
shaderscene.weather.rain_splash_scale = weather.rain_splash_scale;
shaderscene.weather.rain_color = weather.rain_color;
shaderscene.ddgi.grid_dimensions = ddgi.grid_dimensions;
shaderscene.ddgi.probe_count = ddgi.grid_dimensions.x * ddgi.grid_dimensions.y * ddgi.grid_dimensions.z;
shaderscene.ddgi.color_texture_resolution = uint2(ddgi.color_texture.desc.width, ddgi.color_texture.desc.height);
shaderscene.ddgi.color_texture_resolution_rcp = float2(1.0f / shaderscene.ddgi.color_texture_resolution.x, 1.0f / shaderscene.ddgi.color_texture_resolution.y);
shaderscene.ddgi.depth_texture_resolution = uint2(ddgi.depth_texture.desc.width, ddgi.depth_texture.desc.height);
shaderscene.ddgi.depth_texture_resolution_rcp = float2(1.0f / shaderscene.ddgi.depth_texture_resolution.x, 1.0f / shaderscene.ddgi.depth_texture_resolution.y);
shaderscene.ddgi.color_texture = device->GetDescriptorIndex(&ddgi.color_texture, SubresourceType::SRV);
shaderscene.ddgi.depth_texture = device->GetDescriptorIndex(&ddgi.depth_texture, SubresourceType::SRV);
shaderscene.ddgi.offset_texture = device->GetDescriptorIndex(&ddgi.offset_texture, SubresourceType::SRV);
shaderscene.ddgi.grid_min = ddgi.grid_min;
shaderscene.ddgi.grid_extents.x = abs(ddgi.grid_max.x - ddgi.grid_min.x);
shaderscene.ddgi.grid_extents.y = abs(ddgi.grid_max.y - ddgi.grid_min.y);
shaderscene.ddgi.grid_extents.z = abs(ddgi.grid_max.z - ddgi.grid_min.z);
shaderscene.ddgi.grid_extents_rcp.x = 1.0f / shaderscene.ddgi.grid_extents.x;
shaderscene.ddgi.grid_extents_rcp.y = 1.0f / shaderscene.ddgi.grid_extents.y;
shaderscene.ddgi.grid_extents_rcp.z = 1.0f / shaderscene.ddgi.grid_extents.z;
shaderscene.ddgi.smooth_backface = ddgi.smooth_backface;
shaderscene.ddgi.cell_size.x = shaderscene.ddgi.grid_extents.x / (ddgi.grid_dimensions.x - 1);
shaderscene.ddgi.cell_size.y = shaderscene.ddgi.grid_extents.y / (ddgi.grid_dimensions.y - 1);
shaderscene.ddgi.cell_size.z = shaderscene.ddgi.grid_extents.z / (ddgi.grid_dimensions.z - 1);
shaderscene.ddgi.cell_size_rcp.x = 1.0f / shaderscene.ddgi.cell_size.x;
shaderscene.ddgi.cell_size_rcp.y = 1.0f / shaderscene.ddgi.cell_size.y;
shaderscene.ddgi.cell_size_rcp.z = 1.0f / shaderscene.ddgi.cell_size.z;
shaderscene.ddgi.max_distance = std::max(shaderscene.ddgi.cell_size.x, std::max(shaderscene.ddgi.cell_size.y, shaderscene.ddgi.cell_size.z)) * 1.5f;
shaderscene.terrain.init();
if (terrains.GetCount() > 0)
{
shaderscene.terrain = terrains[0].GetShaderTerrain();
}
shaderscene.voxelgrid.init();
if (voxel_grids.GetCount() > 0)
{
VoxelGrid& voxelgrid = voxel_grids[0];
const uint64_t required_size = voxelgrid.voxels.size() * sizeof(uint64_t);
if (voxelgrid_gpu.desc.size < required_size)
{
GPUBufferDesc desc;
desc.size = required_size;
desc.bind_flags = BindFlag::SHADER_RESOURCE;
desc.misc_flags = ResourceMiscFlag::BUFFER_RAW;
device->CreateBuffer(&desc, nullptr, &voxelgrid_gpu);
device->SetName(&voxelgrid_gpu, "voxelgrid_gpu");
}
shaderscene.voxelgrid.buffer = device->GetDescriptorIndex(&voxelgrid_gpu, SubresourceType::SRV);
shaderscene.voxelgrid.resolution = voxelgrid.resolution;
shaderscene.voxelgrid.resolution_div4 = voxelgrid.resolution_div4;
shaderscene.voxelgrid.resolution_rcp = voxelgrid.resolution_rcp;
shaderscene.voxelgrid.center = voxelgrid.center;
shaderscene.voxelgrid.voxelSize = voxelgrid.voxelSize;
shaderscene.voxelgrid.voxelSize_rcp = voxelgrid.voxelSize_rcp;
}
}
void Scene::Clear()
{
for(auto& entry : componentLibrary.entries)
{
entry.second.component_manager->Clear();
}
TLAS = RaytracingAccelerationStructure();
BVH.Clear();
waterRipples.clear();
surfelgi = {};
ddgi = {};
aabb_objects.clear();
aabb_lights.clear();
aabb_decals.clear();
aabb_probes.clear();
matrix_objects.clear();
matrix_objects_prev.clear();
collider_count_cpu = 0;
collider_count_gpu = 0;
}
void Scene::Merge(Scene& other)
{
for (auto& entry : componentLibrary.entries)
{
entry.second.component_manager->Merge(*other.componentLibrary.entries[entry.first].component_manager);
}
bounds = AABB::Merge(bounds, other.bounds);
if (!ddgi.color_texture.IsValid() && other.ddgi.color_texture.IsValid())
{
ddgi = std::move(other.ddgi);
}
aabb_objects.insert(aabb_objects.end(), other.aabb_objects.begin(), other.aabb_objects.end());
aabb_lights.insert(aabb_lights.end(), other.aabb_lights.begin(), other.aabb_lights.end());
aabb_decals.insert(aabb_decals.end(), other.aabb_decals.begin(), other.aabb_decals.end());
aabb_probes.insert(aabb_probes.end(), other.aabb_probes.begin(), other.aabb_probes.end());
matrix_objects.insert(matrix_objects.end(), other.matrix_objects.begin(), other.matrix_objects.end());
matrix_objects_prev.insert(matrix_objects_prev.end(), other.matrix_objects_prev.begin(), other.matrix_objects_prev.end());
// Recount colliders:
collider_allocator_cpu.store(0u);
collider_allocator_gpu.store(0u);
collider_deinterleaved_data.reserve(
sizeof(wi::primitive::AABB) * colliders.GetCount() +
sizeof(ColliderComponent) * colliders.GetCount() +
sizeof(ColliderComponent) * colliders.GetCount()
);
aabb_colliders_cpu = (wi::primitive::AABB*)collider_deinterleaved_data.data();
colliders_cpu = (ColliderComponent*)(aabb_colliders_cpu + colliders.GetCount());
colliders_gpu = colliders_cpu + colliders.GetCount();
for (size_t i = 0; i < colliders.GetCount(); ++i)
{
ColliderComponent& collider = colliders[i];
Entity entity = colliders.GetEntity(i);
const TransformComponent* transform = transforms.GetComponent(entity);
if (transform == nullptr)
return;
XMFLOAT3 scale = transform->GetScale();
collider.sphere.radius = collider.radius * std::max(scale.x, std::max(scale.y, scale.z));
collider.capsule.radius = collider.sphere.radius;
XMMATRIX W = XMLoadFloat4x4(&transform->world);
XMVECTOR offset = XMLoadFloat3(&collider.offset);
XMVECTOR tail = XMLoadFloat3(&collider.tail);
offset = XMVector3Transform(offset, W);
tail = XMVector3Transform(tail, W);
XMStoreFloat3(&collider.sphere.center, offset);
XMVECTOR N = XMVector3Normalize(offset - tail);
offset += N * collider.capsule.radius;
tail -= N * collider.capsule.radius;
XMStoreFloat3(&collider.capsule.base, offset);
XMStoreFloat3(&collider.capsule.tip, tail);
AABB aabb;
switch (collider.shape)
{
default:
case ColliderComponent::Shape::Sphere:
aabb.createFromHalfWidth(collider.sphere.center, XMFLOAT3(collider.sphere.radius, collider.sphere.radius, collider.sphere.radius));
break;
case ColliderComponent::Shape::Capsule:
aabb = collider.capsule.getAABB();
break;
case ColliderComponent::Shape::Plane:
{
collider.plane.origin = collider.sphere.center;
XMVECTOR N = XMVectorSet(0, 1, 0, 0);
N = XMVector3Normalize(XMVector3TransformNormal(N, W));
XMStoreFloat3(&collider.plane.normal, N);
aabb.createFromHalfWidth(XMFLOAT3(0, 0, 0), XMFLOAT3(1, 1, 1));
XMMATRIX PLANE = XMMatrixScaling(collider.radius, 1, collider.radius);
PLANE = PLANE * XMMatrixTranslationFromVector(XMLoadFloat3(&collider.offset));
PLANE = PLANE * W;
aabb = aabb.transform(PLANE);
PLANE = XMMatrixInverse(nullptr, PLANE);
XMStoreFloat4x4(&collider.plane.projection, PLANE);
}
break;
}
if (collider.IsCPUEnabled())
{
uint32_t index = collider_allocator_cpu.fetch_add(1u);
colliders_cpu[index] = collider;
aabb_colliders_cpu[index] = aabb;
}
if (collider.IsGPUEnabled())
{
uint32_t index = collider_allocator_gpu.fetch_add(1u);
colliders_gpu[index] = collider;
}
}
collider_count_cpu = collider_allocator_cpu.load();
collider_count_gpu = collider_allocator_gpu.load();
collider_bvh.Build(aabb_colliders_cpu, collider_count_cpu);
}
Entity Scene::Instantiate(Scene& prefab, bool attached)
{
// Duplicate prefab into tmp scene
Scene tmp;
wi::Archive archive;
archive.SetReadModeAndResetPos(false);
prefab.Serialize(archive);
archive.SetReadModeAndResetPos(true);
tmp.Serialize(archive);
Entity rootEntity = INVALID_ENTITY;
if (attached)
{
// Create root entity
rootEntity = CreateEntity();
tmp.transforms.Create(rootEntity);
tmp.layers.Create(rootEntity).layerMask = ~0;
// Parent all unparented transforms to new root entity
for (size_t i = 0; i < tmp.transforms.GetCount(); ++i)
{
Entity entity = tmp.transforms.GetEntity(i);
if (entity != rootEntity && !tmp.hierarchy.Contains(entity))
{
tmp.Component_Attach(entity, rootEntity);
}
}
}
Merge(tmp);
return rootEntity;
}
void Scene::FindAllEntities(wi::unordered_set<wi::ecs::Entity>& entities) const
{
for (auto& entry : componentLibrary.entries)
{
entities.insert(entry.second.component_manager->GetEntityArray().begin(), entry.second.component_manager->GetEntityArray().end());
}
}
void Scene::Entity_Remove(Entity entity, bool recursive, bool keep_sorted)
{
if (recursive)
{
wi::vector<Entity> entities_to_remove;
for (size_t i = 0; i < hierarchy.GetCount(); ++i)
{
const HierarchyComponent& hier = hierarchy[i];
if (hier.parentID == entity)
{
Entity child = hierarchy.GetEntity(i);
entities_to_remove.push_back(child);
}
}
for (auto& child : entities_to_remove)
{
Entity_Remove(child);
}
}
for (auto& entry : componentLibrary.entries)
{
if (keep_sorted)
{
entry.second.component_manager->Remove_KeepSorted(entity);
}
else
{
entry.second.component_manager->Remove(entity);
}
}
}
Entity Scene::Entity_FindByName(const std::string& name, Entity ancestor)
{
for (size_t i = 0; i < names.GetCount(); ++i)
{
if (names[i] == name)
{
Entity entity = names.GetEntity(i);
if (ancestor != INVALID_ENTITY && !Entity_IsDescendant(entity, ancestor))
continue;
return entity;
}
}
return INVALID_ENTITY;
}
Entity Scene::Entity_Duplicate(Entity entity)
{
wi::Archive archive;
EntitySerializer seri;
// First write the root entity to staging area:
archive.SetReadModeAndResetPos(false);
Entity_Serialize(archive, seri, entity, EntitySerializeFlags::RECURSIVE);
// Then deserialize root:
archive.SetReadModeAndResetPos(true);
Entity root = Entity_Serialize(archive, seri, INVALID_ENTITY, EntitySerializeFlags::RECURSIVE | EntitySerializeFlags::KEEP_INTERNAL_ENTITY_REFERENCES);
return root;
}
bool Scene::Entity_IsDescendant(wi::ecs::Entity entity, wi::ecs::Entity ancestor) const
{
const HierarchyComponent* hier = hierarchy.GetComponent(entity);
while (hier != nullptr)
{
if (hier->parentID == ancestor)
return true;
hier = hierarchy.GetComponent(hier->parentID);
}
return false;
}
Entity Scene::Entity_CreateTransform(
const std::string& name
)
{
Entity entity = CreateEntity();
names.Create(entity) = name;
transforms.Create(entity);
return entity;
}
Entity Scene::Entity_CreateMaterial(
const std::string& name
)
{
Entity entity = CreateEntity();
names.Create(entity) = name;
materials.Create(entity);
return entity;
}
Entity Scene::Entity_CreateObject(
const std::string& name
)
{
Entity entity = CreateEntity();
names.Create(entity) = name;
layers.Create(entity);
transforms.Create(entity);
objects.Create(entity);
return entity;
}
Entity Scene::Entity_CreateMesh(
const std::string& name
)
{
Entity entity = CreateEntity();
names.Create(entity) = name;
meshes.Create(entity);
return entity;
}
Entity Scene::Entity_CreateLight(
const std::string& name,
const XMFLOAT3& position,
const XMFLOAT3& color,
float intensity,
float range,
LightComponent::LightType type,
float outerConeAngle,
float innerConeAngle)
{
Entity entity = CreateEntity();
names.Create(entity) = name;
layers.Create(entity);
TransformComponent& transform = transforms.Create(entity);
transform.Translate(position);
transform.UpdateTransform();
LightComponent& light = lights.Create(entity);
light.intensity = intensity;
light.range = range;
light.color = color;
light.SetType(type);
light.outerConeAngle = outerConeAngle;
light.innerConeAngle = innerConeAngle;
return entity;
}
Entity Scene::Entity_CreateForce(
const std::string& name,
const XMFLOAT3& position
)
{
Entity entity = CreateEntity();
names.Create(entity) = name;
layers.Create(entity);
TransformComponent& transform = transforms.Create(entity);
transform.Translate(position);
transform.UpdateTransform();
ForceFieldComponent& force = forces.Create(entity);
return entity;
}
Entity Scene::Entity_CreateEnvironmentProbe(
const std::string& name,
const XMFLOAT3& position
)
{
Entity entity = CreateEntity();
names.Create(entity) = name;
layers.Create(entity);
TransformComponent& transform = transforms.Create(entity);
transform.Translate(position);
transform.UpdateTransform();
probes.Create(entity);
return entity;
}
Entity Scene::Entity_CreateDecal(
const std::string& name,
const std::string& textureName,
const std::string& normalMapName
)
{
Entity entity = CreateEntity();
names.Create(entity) = name;
layers.Create(entity);
transforms.Create(entity);
decals.Create(entity);
MaterialComponent& material = materials.Create(entity);
material.textures[MaterialComponent::BASECOLORMAP].name = textureName;
material.textures[MaterialComponent::NORMALMAP].name = normalMapName;
material.CreateRenderData();
return entity;
}
Entity Scene::Entity_CreateCamera(
const std::string& name,
float width, float height, float nearPlane, float farPlane, float fov
)
{
Entity entity = CreateEntity();
names.Create(entity) = name;
layers.Create(entity);
transforms.Create(entity);
CameraComponent& camera = cameras.Create(entity);
camera.CreatePerspective(width, height, nearPlane, farPlane, fov);
return entity;
}
Entity Scene::Entity_CreateEmitter(
const std::string& name,
const XMFLOAT3& position
)
{
Entity entity = CreateEntity();
names.Create(entity) = name;
emitters.Create(entity).count = 10;
TransformComponent& transform = transforms.Create(entity);
transform.Translate(position);
transform.UpdateTransform();
materials.Create(entity).userBlendMode = BLENDMODE_ALPHA;
return entity;
}
Entity Scene::Entity_CreateHair(
const std::string& name,
const XMFLOAT3& position
)
{
Entity entity = CreateEntity();
names.Create(entity) = name;
hairs.Create(entity);
TransformComponent& transform = transforms.Create(entity);
transform.Translate(position);
transform.UpdateTransform();
materials.Create(entity);
return entity;
}
Entity Scene::Entity_CreateSound(
const std::string& name,
const std::string& filename,
const XMFLOAT3& position
)
{
Entity entity = CreateEntity();
names.Create(entity) = name;
if (!filename.empty())
{
SoundComponent& sound = sounds.Create(entity);
sound.filename = filename;
sound.soundResource = wi::resourcemanager::Load(filename, wi::resourcemanager::Flags::IMPORT_RETAIN_FILEDATA);
wi::audio::CreateSoundInstance(&sound.soundResource.GetSound(), &sound.soundinstance);
}
TransformComponent& transform = transforms.Create(entity);
transform.Translate(position);
transform.UpdateTransform();
return entity;
}
Entity Scene::Entity_CreateVideo(
const std::string& name,
const std::string& filename
)
{
Entity entity = CreateEntity();
names.Create(entity) = name;
if (!filename.empty())
{
VideoComponent& video = videos.Create(entity);
video.filename = filename;
video.videoResource = wi::resourcemanager::Load(filename, wi::resourcemanager::Flags::IMPORT_RETAIN_FILEDATA);
wi::video::CreateVideoInstance(&video.videoResource.GetVideo(), &video.videoinstance);
}
return entity;
}
Entity Scene::Entity_CreateCube(
const std::string& name
)
{
Entity entity = CreateEntity();
if (!name.empty())
{
names.Create(entity) = name;
}
layers.Create(entity);
transforms.Create(entity);
ObjectComponent& object = objects.Create(entity);
MeshComponent& mesh = meshes.Create(entity);
// object references the mesh entity (there can be multiple objects referencing one mesh):
object.meshID = entity;
mesh.vertex_positions = {
// -Z
XMFLOAT3(-1,1, -1),
XMFLOAT3(-1,-1, -1),
XMFLOAT3(1,-1, -1),
XMFLOAT3(1,1, -1),
// +Z
XMFLOAT3(-1,1, 1),
XMFLOAT3(-1,-1, 1),
XMFLOAT3(1,-1, 1),
XMFLOAT3(1,1, 1),
// -X
XMFLOAT3(-1, -1,1),
XMFLOAT3(-1, -1,-1),
XMFLOAT3(-1, 1,-1),
XMFLOAT3(-1, 1,1),
// +X
XMFLOAT3(1, -1,1),
XMFLOAT3(1, -1,-1),
XMFLOAT3(1, 1,-1),
XMFLOAT3(1, 1,1),
// -Y
XMFLOAT3(-1, -1,1),
XMFLOAT3(-1, -1,-1),
XMFLOAT3(1, -1,-1),
XMFLOAT3(1, -1,1),
// +Y
XMFLOAT3(-1, 1,1),
XMFLOAT3(-1, 1,-1),
XMFLOAT3(1, 1,-1),
XMFLOAT3(1, 1,1),
};
mesh.vertex_normals = {
XMFLOAT3(0,0,-1),
XMFLOAT3(0,0,-1),
XMFLOAT3(0,0,-1),
XMFLOAT3(0,0,-1),
XMFLOAT3(0,0,1),
XMFLOAT3(0,0,1),
XMFLOAT3(0,0,1),
XMFLOAT3(0,0,1),
XMFLOAT3(-1,0,0),
XMFLOAT3(-1,0,0),
XMFLOAT3(-1,0,0),
XMFLOAT3(-1,0,0),
XMFLOAT3(1,0,0),
XMFLOAT3(1,0,0),
XMFLOAT3(1,0,0),
XMFLOAT3(1,0,0),
XMFLOAT3(0,-1,0),
XMFLOAT3(0,-1,0),
XMFLOAT3(0,-1,0),
XMFLOAT3(0,-1,0),
XMFLOAT3(0,1,0),
XMFLOAT3(0,1,0),
XMFLOAT3(0,1,0),
XMFLOAT3(0,1,0),
};
mesh.vertex_uvset_0 = {
XMFLOAT2(0,0),
XMFLOAT2(0,1),
XMFLOAT2(1,1),
XMFLOAT2(1,0),
XMFLOAT2(0,0),
XMFLOAT2(0,1),
XMFLOAT2(1,1),
XMFLOAT2(1,0),
XMFLOAT2(0,0),
XMFLOAT2(0,1),
XMFLOAT2(1,1),
XMFLOAT2(1,0),
XMFLOAT2(0,0),
XMFLOAT2(0,1),
XMFLOAT2(1,1),
XMFLOAT2(1,0),
XMFLOAT2(0,0),
XMFLOAT2(0,1),
XMFLOAT2(1,1),
XMFLOAT2(1,0),
XMFLOAT2(0,0),
XMFLOAT2(0,1),
XMFLOAT2(1,1),
XMFLOAT2(1,0),
};
mesh.indices = {
0, 1, 2, 0, 2, 3,
0 + 4, 2 + 4, 1 + 4, 0 + 4, 3 + 4, 2 + 4, // swapped winding
0 + 4 * 2, 1 + 4 * 2, 2 + 4 * 2, 0 + 4 * 2, 2 + 4 * 2, 3 + 4 * 2,
0 + 4 * 3, 2 + 4 * 3, 1 + 4 * 3, 0 + 4 * 3, 3 + 4 * 3, 2 + 4 * 3, // swapped winding
0 + 4 * 4, 2 + 4 * 4, 1 + 4 * 4, 0 + 4 * 4, 3 + 4 * 4, 2 + 4 * 4, // swapped winding
0 + 4 * 5, 1 + 4 * 5, 2 + 4 * 5, 0 + 4 * 5, 2 + 4 * 5, 3 + 4 * 5,
};
// Subset maps a part of the mesh to a material:
MeshComponent::MeshSubset& subset = mesh.subsets.emplace_back();
subset.indexCount = uint32_t(mesh.indices.size());
materials.Create(entity);
subset.materialID = entity; // the material component is created on the same entity as the mesh component, though it is not required as it could also use a different material entity
// vertex buffer GPU data will be packed and uploaded here:
mesh.CreateRenderData();
return entity;
}
Entity Scene::Entity_CreatePlane(
const std::string& name
)
{
Entity entity = CreateEntity();
if (!name.empty())
{
names.Create(entity) = name;
}
layers.Create(entity);
transforms.Create(entity);
ObjectComponent& object = objects.Create(entity);
MeshComponent& mesh = meshes.Create(entity);
// object references the mesh entity (there can be multiple objects referencing one mesh):
object.meshID = entity;
mesh.vertex_positions = {
// +Y
XMFLOAT3(-1, 0,1),
XMFLOAT3(-1, 0,-1),
XMFLOAT3(1, 0,-1),
XMFLOAT3(1, 0,1),
};
mesh.vertex_normals = {
XMFLOAT3(0,1,0),
XMFLOAT3(0,1,0),
XMFLOAT3(0,1,0),
XMFLOAT3(0,1,0),
};
mesh.vertex_uvset_0 = {
XMFLOAT2(0,0),
XMFLOAT2(0,1),
XMFLOAT2(1,1),
XMFLOAT2(1,0),
};
mesh.indices = {
0, 1, 2, 0, 2, 3,
};
// Subset maps a part of the mesh to a material:
MeshComponent::MeshSubset& subset = mesh.subsets.emplace_back();
subset.indexCount = uint32_t(mesh.indices.size());
materials.Create(entity);
subset.materialID = entity; // the material component is created on the same entity as the mesh component, though it is not required as it could also use a different material entity
// vertex buffer GPU data will be packed and uploaded here:
mesh.CreateRenderData();
return entity;
}
Entity Scene::Entity_CreateSphere(
const std::string& name,
float radius,
uint32_t latitudeBands,
uint32_t longitudeBands
)
{
Entity entity = CreateEntity();
if (!name.empty())
{
names.Create(entity) = name;
}
layers.Create(entity);
transforms.Create(entity);
ObjectComponent& object = objects.Create(entity);
MeshComponent& mesh = meshes.Create(entity);
// object references the mesh entity (there can be multiple objects referencing one mesh):
object.meshID = entity;
for (uint32_t latNumber = 0; latNumber <= latitudeBands; latNumber++)
{
float theta = float(latNumber) * XM_PI / float(latitudeBands);
float sinTheta = sin(theta);
float cosTheta = cos(theta);
for (uint32_t longNumber = 0; longNumber <= longitudeBands; longNumber++)
{
float phi = float(longNumber) * 2 * XM_PI / float(longitudeBands);
float sinPhi = sin(phi);
float cosPhi = cos(phi);
XMFLOAT3& position = mesh.vertex_positions.emplace_back();
XMFLOAT3& normal = mesh.vertex_normals.emplace_back();
XMFLOAT2& uv = mesh.vertex_uvset_0.emplace_back();
normal.x = cosPhi * sinTheta; // x
normal.y = cosTheta; // y
normal.z = sinPhi * sinTheta; // z
uv.x = float(longNumber) / float(longitudeBands); // u
uv.y = float(latNumber) / float(latitudeBands); // v
position.x = radius * normal.x;
position.y = radius * normal.y;
position.z = radius * normal.z;
}
}
for (uint32_t latNumber = 0; latNumber < latitudeBands; latNumber++)
{
for (uint32_t longNumber = 0; longNumber < longitudeBands; longNumber++)
{
uint32_t first = (latNumber * (longitudeBands + 1)) + longNumber;
uint32_t second = first + longitudeBands + 1;
mesh.indices.push_back(first);
mesh.indices.push_back(second);
mesh.indices.push_back(first + 1);
mesh.indices.push_back(second);
mesh.indices.push_back(second + 1);
mesh.indices.push_back(first + 1);
}
}
// Subset maps a part of the mesh to a material:
MeshComponent::MeshSubset& subset = mesh.subsets.emplace_back();
subset.indexCount = uint32_t(mesh.indices.size());
materials.Create(entity);
subset.materialID = entity; // the material component is created on the same entity as the mesh component, though it is not required as it could also use a different material entity
// vertex buffer GPU data will be packed and uploaded here:
mesh.CreateRenderData();
return entity;
}
void Scene::Component_Attach(Entity entity, Entity parent, bool child_already_in_local_space)
{
assert(entity != parent);
if (hierarchy.Contains(entity))
{
Component_Detach(entity);
}
HierarchyComponent& parentcomponent = hierarchy.Create(entity);
parentcomponent.parentID = parent;
TransformComponent* transform_parent = transforms.GetComponent(parent);
TransformComponent* transform_child = transforms.GetComponent(entity);
if (transform_parent != nullptr && transform_child != nullptr)
{
if (!child_already_in_local_space)
{
XMMATRIX B = XMMatrixInverse(nullptr, XMLoadFloat4x4(&transform_parent->world));
transform_child->MatrixTransform(B);
transform_child->UpdateTransform();
}
transform_child->UpdateTransform_Parented(*transform_parent);
}
}
void Scene::Component_Detach(Entity entity)
{
const HierarchyComponent* parent = hierarchy.GetComponent(entity);
if (parent != nullptr)
{
TransformComponent* transform = transforms.GetComponent(entity);
if (transform != nullptr)
{
transform->ApplyTransform();
}
LayerComponent* layer = layers.GetComponent(entity);
if (layer != nullptr)
{
layer->propagationMask = ~0;
}
hierarchy.Remove(entity);
}
}
void Scene::Component_DetachChildren(Entity parent)
{
for (size_t i = 0; i < hierarchy.GetCount(); )
{
if (hierarchy[i].parentID == parent)
{
Entity entity = hierarchy.GetEntity(i);
Component_Detach(entity);
}
else
{
++i;
}
}
}
void Scene::RunAnimationUpdateSystem(wi::jobsystem::context& ctx)
{
auto range = wi::profiler::BeginRangeCPU("Animations");
wi::jobsystem::Wait(animation_dependency_scan_workload);
wi::jobsystem::Dispatch(ctx, (uint32_t)animation_queue_count, 1, [&](wi::jobsystem::JobArgs args) {
AnimationQueue& animation_queue = animation_queues[args.jobIndex];
for (size_t animation_index = 0; animation_index < animation_queue.animations.size(); ++animation_index)
{
AnimationComponent& animation = *animation_queue.animations[animation_index];
animation.last_update_time = animation.timer;
for (const AnimationComponent::AnimationChannel& channel : animation.channels)
{
assert(channel.samplerIndex < (int)animation.samplers.size());
const AnimationComponent::AnimationSampler& sampler = animation.samplers[channel.samplerIndex];
const Scene* data_scene = sampler.scene == nullptr ? this : (const Scene*)sampler.scene;
const AnimationDataComponent* animationdata = data_scene->animation_datas.GetComponent(sampler.data);
if (animationdata == nullptr)
continue;
if (animationdata->keyframe_times.empty())
continue;
const AnimationComponent::AnimationChannel::PathDataType path_data_type = channel.GetPathDataType();
float timeFirst = std::numeric_limits<float>::max();
float timeLast = std::numeric_limits<float>::min();
int keyLeft = 0; float timeLeft = std::numeric_limits<float>::min();
int keyRight = 0; float timeRight = std::numeric_limits<float>::max();
// search for usable keyframes:
for (int k = 0; k < (int)animationdata->keyframe_times.size(); ++k)
{
const float time = animationdata->keyframe_times[k];
if (time < timeFirst)
{
timeFirst = time;
}
if (time > timeLast)
{
timeLast = time;
}
if (time <= animation.timer && time > timeLeft)
{
timeLeft = time;
keyLeft = k;
}
if (time >= animation.timer && time < timeRight)
{
timeRight = time;
keyRight = k;
}
}
if (path_data_type != AnimationComponent::AnimationChannel::PathDataType::Event)
{
if (animation.timer < timeFirst)
{
// animation beginning haven't been reached, force first keyframe:
timeLeft = timeFirst;
timeRight = timeFirst;
keyLeft = 0;
keyRight = 0;
}
}
else
{
timeLeft = std::max(timeLeft, timeFirst);
timeRight = std::max(timeRight, timeLast);
}
const float left = animationdata->keyframe_times[keyLeft];
const float right = animationdata->keyframe_times[keyRight];
union Interpolator
{
XMFLOAT4 f4;
XMFLOAT3 f3;
XMFLOAT2 f2;
float f;
} interpolator = {};
TransformComponent* target_transform = nullptr;
MeshComponent* target_mesh = nullptr;
LightComponent* target_light = nullptr;
SoundComponent* target_sound = nullptr;
EmittedParticleSystem* target_emitter = nullptr;
CameraComponent* target_camera = nullptr;
ScriptComponent* target_script = nullptr;
MaterialComponent* target_material = nullptr;
if (
channel.path == AnimationComponent::AnimationChannel::Path::TRANSLATION ||
channel.path == AnimationComponent::AnimationChannel::Path::ROTATION ||
channel.path == AnimationComponent::AnimationChannel::Path::SCALE
)
{
target_transform = transforms.GetComponent(channel.target);
if (target_transform == nullptr)
continue;
switch (channel.path)
{
case AnimationComponent::AnimationChannel::Path::TRANSLATION:
interpolator.f3 = target_transform->translation_local;
break;
case AnimationComponent::AnimationChannel::Path::ROTATION:
interpolator.f4 = target_transform->rotation_local;
break;
case AnimationComponent::AnimationChannel::Path::SCALE:
interpolator.f3 = target_transform->scale_local;
break;
default:
break;
}
}
else if (channel.path == AnimationComponent::AnimationChannel::Path::WEIGHTS)
{
target_mesh = meshes.GetComponent(channel.target);
if (target_mesh == nullptr)
{
// Also try going through object's mesh reference:
ObjectComponent* object = objects.GetComponent(channel.target);
if (object == nullptr)
continue;
target_mesh = meshes.GetComponent(object->meshID);
}
if (target_mesh == nullptr)
continue;
animation.morph_weights_temp.resize(target_mesh->morph_targets.size());
}
else if (
channel.path >= AnimationComponent::AnimationChannel::Path::LIGHT_COLOR &&
channel.path < AnimationComponent::AnimationChannel::Path::_LIGHT_RANGE_END
)
{
target_light = lights.GetComponent(channel.target);
if (target_light == nullptr)
continue;
switch (channel.path)
{
case AnimationComponent::AnimationChannel::Path::LIGHT_COLOR:
interpolator.f3 = target_light->color;
break;
case AnimationComponent::AnimationChannel::Path::LIGHT_INTENSITY:
interpolator.f = target_light->intensity;
break;
case AnimationComponent::AnimationChannel::Path::LIGHT_RANGE:
interpolator.f = target_light->range;
break;
case AnimationComponent::AnimationChannel::Path::LIGHT_INNERCONE:
interpolator.f = target_light->innerConeAngle;
break;
case AnimationComponent::AnimationChannel::Path::LIGHT_OUTERCONE:
interpolator.f = target_light->outerConeAngle;
break;
default:
break;
}
}
else if (
channel.path >= AnimationComponent::AnimationChannel::Path::SOUND_PLAY &&
channel.path < AnimationComponent::AnimationChannel::Path::_SOUND_RANGE_END
)
{
target_sound = sounds.GetComponent(channel.target);
if (target_sound == nullptr)
continue;
switch (channel.path)
{
case AnimationComponent::AnimationChannel::Path::SOUND_VOLUME:
interpolator.f = target_sound->volume;
break;
default:
break;
}
}
else if (
channel.path >= AnimationComponent::AnimationChannel::Path::EMITTER_EMITCOUNT &&
channel.path < AnimationComponent::AnimationChannel::Path::_EMITTER_RANGE_END
)
{
target_emitter = emitters.GetComponent(channel.target);
if (target_emitter == nullptr)
continue;
switch (channel.path)
{
case AnimationComponent::AnimationChannel::Path::EMITTER_EMITCOUNT:
interpolator.f = target_emitter->count;
break;
default:
break;
}
}
else if (
channel.path >= AnimationComponent::AnimationChannel::Path::CAMERA_FOV &&
channel.path < AnimationComponent::AnimationChannel::Path::_CAMERA_RANGE_END
)
{
target_camera = cameras.GetComponent(channel.target);
if (target_camera == nullptr)
continue;
switch (channel.path)
{
case AnimationComponent::AnimationChannel::Path::CAMERA_FOV:
interpolator.f = target_camera->fov;
break;
case AnimationComponent::AnimationChannel::Path::CAMERA_FOCAL_LENGTH:
interpolator.f = target_camera->focal_length;
break;
case AnimationComponent::AnimationChannel::Path::CAMERA_APERTURE_SIZE:
interpolator.f = target_camera->aperture_size;
break;
case AnimationComponent::AnimationChannel::Path::CAMERA_APERTURE_SHAPE:
interpolator.f2 = target_camera->aperture_shape;
break;
default:
break;
}
}
else if (
channel.path >= AnimationComponent::AnimationChannel::Path::SCRIPT_PLAY &&
channel.path < AnimationComponent::AnimationChannel::Path::_SCRIPT_RANGE_END
)
{
target_script = scripts.GetComponent(channel.target);
if (target_script == nullptr)
continue;
}
else if (
channel.path >= AnimationComponent::AnimationChannel::Path::MATERIAL_COLOR &&
channel.path < AnimationComponent::AnimationChannel::Path::_MATERIAL_RANGE_END
)
{
target_material = materials.GetComponent(channel.target);
if (target_material == nullptr)
continue;
switch (channel.path)
{
case AnimationComponent::AnimationChannel::Path::MATERIAL_COLOR:
interpolator.f4 = target_material->baseColor;
break;
case AnimationComponent::AnimationChannel::Path::MATERIAL_EMISSIVE:
interpolator.f4 = target_material->emissiveColor;
break;
case AnimationComponent::AnimationChannel::Path::MATERIAL_ROUGHNESS:
interpolator.f = target_material->roughness;
break;
case AnimationComponent::AnimationChannel::Path::MATERIAL_METALNESS:
interpolator.f = target_material->metalness;
break;
case AnimationComponent::AnimationChannel::Path::MATERIAL_REFLECTANCE:
interpolator.f = target_material->reflectance;
break;
case AnimationComponent::AnimationChannel::Path::MATERIAL_TEXMULADD:
interpolator.f4 = target_material->texMulAdd;
break;
default:
break;
}
}
else
{
assert(0);
continue;
}
if (path_data_type == AnimationComponent::AnimationChannel::PathDataType::Event)
{
// No path data, only event trigger:
if (keyLeft == channel.next_event && animation.timer >= timeLeft)
{
channel.next_event++;
switch (channel.path)
{
case AnimationComponent::AnimationChannel::Path::SOUND_PLAY:
target_sound->Play();
break;
case AnimationComponent::AnimationChannel::Path::SOUND_STOP:
target_sound->Stop();
break;
case AnimationComponent::AnimationChannel::Path::SCRIPT_PLAY:
target_script->Play();
break;
case AnimationComponent::AnimationChannel::Path::SCRIPT_STOP:
target_script->Stop();
break;
default:
break;
}
}
}
else
{
// Path data interpolation:
switch (sampler.mode)
{
default:
case AnimationComponent::AnimationSampler::Mode::STEP:
{
// Nearest neighbor method:
const int key = wi::math::InverseLerp(timeLeft, timeRight, animation.timer) > 0.5f ? keyRight : keyLeft;
switch (path_data_type)
{
default:
case AnimationComponent::AnimationChannel::PathDataType::Float:
{
assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size());
interpolator.f = animationdata->keyframe_data[key];
}
break;
case AnimationComponent::AnimationChannel::PathDataType::Float2:
{
assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size() * 2);
interpolator.f2 = ((const XMFLOAT2*)animationdata->keyframe_data.data())[key];
}
break;
case AnimationComponent::AnimationChannel::PathDataType::Float3:
{
assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size() * 3);
interpolator.f3 = ((const XMFLOAT3*)animationdata->keyframe_data.data())[key];
}
break;
case AnimationComponent::AnimationChannel::PathDataType::Float4:
{
assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size() * 4);
interpolator.f4 = ((const XMFLOAT4*)animationdata->keyframe_data.data())[key];
}
break;
case AnimationComponent::AnimationChannel::PathDataType::Weights:
{
assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size() * animation.morph_weights_temp.size());
for (size_t j = 0; j < animation.morph_weights_temp.size(); ++j)
{
animation.morph_weights_temp[j] = animationdata->keyframe_data[key * animation.morph_weights_temp.size() + j];
}
}
break;
}
}
break;
case AnimationComponent::AnimationSampler::Mode::LINEAR:
{
// Linear interpolation method:
float t;
if (keyLeft == keyRight)
{
t = 0;
}
else
{
t = (animation.timer - left) / (right - left);
}
t = wi::math::saturate(t);
switch (path_data_type)
{
default:
case AnimationComponent::AnimationChannel::PathDataType::Float:
{
assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size());
float vLeft = animationdata->keyframe_data[keyLeft];
float vRight = animationdata->keyframe_data[keyRight];
float vAnim = wi::math::Lerp(vLeft, vRight, t);
interpolator.f = vAnim;
}
break;
case AnimationComponent::AnimationChannel::PathDataType::Float2:
{
assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size() * 2);
const XMFLOAT2* data = (const XMFLOAT2*)animationdata->keyframe_data.data();
XMVECTOR vLeft = XMLoadFloat2(&data[keyLeft]);
XMVECTOR vRight = XMLoadFloat2(&data[keyRight]);
XMVECTOR vAnim = XMVectorLerp(vLeft, vRight, t);
XMStoreFloat2(&interpolator.f2, vAnim);
}
break;
case AnimationComponent::AnimationChannel::PathDataType::Float3:
{
assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size() * 3);
const XMFLOAT3* data = (const XMFLOAT3*)animationdata->keyframe_data.data();
XMVECTOR vLeft = XMLoadFloat3(&data[keyLeft]);
XMVECTOR vRight = XMLoadFloat3(&data[keyRight]);
XMVECTOR vAnim = XMVectorLerp(vLeft, vRight, t);
XMStoreFloat3(&interpolator.f3, vAnim);
}
break;
case AnimationComponent::AnimationChannel::PathDataType::Float4:
{
assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size() * 4);
const XMFLOAT4* data = (const XMFLOAT4*)animationdata->keyframe_data.data();
XMVECTOR vLeft = XMLoadFloat4(&data[keyLeft]);
XMVECTOR vRight = XMLoadFloat4(&data[keyRight]);
XMVECTOR vAnim;
if (channel.path == AnimationComponent::AnimationChannel::Path::ROTATION)
{
vAnim = XMQuaternionSlerp(vLeft, vRight, t);
vAnim = XMQuaternionNormalize(vAnim);
}
else
{
vAnim = XMVectorLerp(vLeft, vRight, t);
}
XMStoreFloat4(&interpolator.f4, vAnim);
}
break;
case AnimationComponent::AnimationChannel::PathDataType::Weights:
{
assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size() * animation.morph_weights_temp.size());
for (size_t j = 0; j < animation.morph_weights_temp.size(); ++j)
{
float vLeft = animationdata->keyframe_data[keyLeft * animation.morph_weights_temp.size() + j];
float vRight = animationdata->keyframe_data[keyRight * animation.morph_weights_temp.size() + j];
float vAnim = wi::math::Lerp(vLeft, vRight, t);
animation.morph_weights_temp[j] = vAnim;
}
}
break;
}
}
break;
case AnimationComponent::AnimationSampler::Mode::CUBICSPLINE:
{
// Cubic Spline interpolation method:
float t;
if (keyLeft == keyRight)
{
t = 0;
}
else
{
t = (animation.timer - left) / (right - left);
}
t = wi::math::saturate(t);
const float t2 = t * t;
const float t3 = t2 * t;
switch (path_data_type)
{
default:
case AnimationComponent::AnimationChannel::PathDataType::Float:
{
assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size());
float vLeft = animationdata->keyframe_data[keyLeft * 3 + 1];
float vLeftTanOut = animationdata->keyframe_data[keyLeft * 3 + 2];
float vRightTanIn = animationdata->keyframe_data[keyRight * 3 + 0];
float vRight = animationdata->keyframe_data[keyRight * 3 + 1];
float vAnim = (2 * t3 - 3 * t2 + 1) * vLeft + (t3 - 2 * t2 + t) * vLeftTanOut + (-2 * t3 + 3 * t2) * vRight + (t3 - t2) * vRightTanIn;
interpolator.f = vAnim;
}
break;
case AnimationComponent::AnimationChannel::PathDataType::Float2:
{
assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size() * 2 * 3);
const XMFLOAT2* data = (const XMFLOAT2*)animationdata->keyframe_data.data();
XMVECTOR vLeft = XMLoadFloat2(&data[keyLeft * 3 + 1]);
XMVECTOR vLeftTanOut = dt * XMLoadFloat2(&data[keyLeft * 3 + 2]);
XMVECTOR vRightTanIn = dt * XMLoadFloat2(&data[keyRight * 3 + 0]);
XMVECTOR vRight = XMLoadFloat2(&data[keyRight * 3 + 1]);
XMVECTOR vAnim = (2 * t3 - 3 * t2 + 1) * vLeft + (t3 - 2 * t2 + t) * vLeftTanOut + (-2 * t3 + 3 * t2) * vRight + (t3 - t2) * vRightTanIn;
XMStoreFloat2(&interpolator.f2, vAnim);
}
break;
case AnimationComponent::AnimationChannel::PathDataType::Float3:
{
assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size() * 3 * 3);
const XMFLOAT3* data = (const XMFLOAT3*)animationdata->keyframe_data.data();
XMVECTOR vLeft = XMLoadFloat3(&data[keyLeft * 3 + 1]);
XMVECTOR vLeftTanOut = dt * XMLoadFloat3(&data[keyLeft * 3 + 2]);
XMVECTOR vRightTanIn = dt * XMLoadFloat3(&data[keyRight * 3 + 0]);
XMVECTOR vRight = XMLoadFloat3(&data[keyRight * 3 + 1]);
XMVECTOR vAnim = (2 * t3 - 3 * t2 + 1) * vLeft + (t3 - 2 * t2 + t) * vLeftTanOut + (-2 * t3 + 3 * t2) * vRight + (t3 - t2) * vRightTanIn;
XMStoreFloat3(&interpolator.f3, vAnim);
}
break;
case AnimationComponent::AnimationChannel::PathDataType::Float4:
{
assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size() * 4 * 3);
const XMFLOAT4* data = (const XMFLOAT4*)animationdata->keyframe_data.data();
XMVECTOR vLeft = XMLoadFloat4(&data[keyLeft * 3 + 1]);
XMVECTOR vLeftTanOut = dt * XMLoadFloat4(&data[keyLeft * 3 + 2]);
XMVECTOR vRightTanIn = dt * XMLoadFloat4(&data[keyRight * 3 + 0]);
XMVECTOR vRight = XMLoadFloat4(&data[keyRight * 3 + 1]);
XMVECTOR vAnim = (2 * t3 - 3 * t2 + 1) * vLeft + (t3 - 2 * t2 + t) * vLeftTanOut + (-2 * t3 + 3 * t2) * vRight + (t3 - t2) * vRightTanIn;
if (channel.path == AnimationComponent::AnimationChannel::Path::ROTATION)
{
vAnim = XMQuaternionNormalize(vAnim);
}
XMStoreFloat4(&interpolator.f4, vAnim);
}
break;
case AnimationComponent::AnimationChannel::PathDataType::Weights:
{
assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size() * animation.morph_weights_temp.size() * 3);
for (size_t j = 0; j < animation.morph_weights_temp.size(); ++j)
{
float vLeft = animationdata->keyframe_data[(keyLeft * animation.morph_weights_temp.size() + j) * 3 + 1];
float vLeftTanOut = animationdata->keyframe_data[(keyLeft * animation.morph_weights_temp.size() + j) * 3 + 2];
float vRightTanIn = animationdata->keyframe_data[(keyRight * animation.morph_weights_temp.size() + j) * 3 + 0];
float vRight = animationdata->keyframe_data[(keyRight * animation.morph_weights_temp.size() + j) * 3 + 1];
float vAnim = (2 * t3 - 3 * t2 + 1) * vLeft + (t3 - 2 * t2 + t) * vLeftTanOut + (-2 * t3 + 3 * t2) * vRight + (t3 - t2) * vRightTanIn;
animation.morph_weights_temp[j] = vAnim;
}
}
break;
}
}
break;
}
}
// The interpolated raw values will be blended on top of component values:
const float t = animation.amount;
// CheckIf this channel is the root motion bone or not.
const bool isRootBone = (animation.IsRootMotion() && animation.rootMotionBone != wi::ecs::INVALID_ENTITY && (target_transform == transforms.GetComponent(animation.rootMotionBone)));
if (target_transform != nullptr)
{
target_transform->SetDirty();
switch (channel.path)
{
case AnimationComponent::AnimationChannel::Path::TRANSLATION:
{
const XMVECTOR aT = XMLoadFloat3(&target_transform->translation_local);
XMVECTOR bT = XMLoadFloat3(&interpolator.f3);
if (channel.retargetIndex >= 0 && channel.retargetIndex < (int)animation.retargets.size())
{
// Retargeting transfer from source to destination:
const AnimationComponent::RetargetSourceData& retarget = animation.retargets[channel.retargetIndex];
TransformComponent* source_transform = data_scene->transforms.GetComponent(retarget.source);
if (source_transform != nullptr)
{
XMMATRIX dstRelativeMatrix = XMLoadFloat4x4(&retarget.dstRelativeMatrix);
XMMATRIX srcRelativeParentMatrix = XMLoadFloat4x4(&retarget.srcRelativeParentMatrix);
XMVECTOR S, R; // matrix decompose destinations
TransformComponent transform = *source_transform;
XMStoreFloat3(&transform.translation_local, bT);
XMMATRIX localMatrix = dstRelativeMatrix * transform.GetLocalMatrix() * srcRelativeParentMatrix;
XMMatrixDecompose(&S, &R, &bT, localMatrix);
}
}
const XMVECTOR T = XMVectorLerp(aT, bT, t);
if (!isRootBone)
{
// Not root motion bone.
XMStoreFloat3(&target_transform->translation_local, T);
}
else
{
if (XMVector4Equal(animation.rootPrevTranslation, animation.INVALID_VECTOR) || animation.end < animation.prevLocTimer)
{
// If root motion bone.
animation.rootPrevTranslation = T;
}
XMVECTOR rotation_quat = animation.rootPrevRotation;
if (XMVector4Equal(animation.rootPrevRotation, animation.INVALID_VECTOR) || animation.end < animation.prevRotTimer)
{
// If root motion bone.
rotation_quat = XMLoadFloat4(&target_transform->rotation_local);
}
const XMVECTOR root_trans = XMVectorSubtract(T, animation.rootPrevTranslation);
XMVECTOR inverseQuaternion = XMQuaternionInverse(rotation_quat);
XMVECTOR rotatedDirectionVector = XMVector3Rotate(root_trans, inverseQuaternion);
XMMATRIX mat = XMLoadFloat4x4(&target_transform->world);
rotatedDirectionVector = XMVector4Transform(rotatedDirectionVector, mat);
// Store root motion offset
XMStoreFloat3(&animation.rootTranslationOffset, rotatedDirectionVector);
// If root motion bone.
animation.rootPrevTranslation = T;
animation.prevLocTimer = animation.timer;
}
}
break;
case AnimationComponent::AnimationChannel::Path::ROTATION:
{
const XMVECTOR aR = XMLoadFloat4(&target_transform->rotation_local);
XMVECTOR bR = XMLoadFloat4(&interpolator.f4);
if (channel.retargetIndex >= 0 && channel.retargetIndex < (int)animation.retargets.size())
{
// Retargeting transfer from source to destination:
const AnimationComponent::RetargetSourceData& retarget = animation.retargets[channel.retargetIndex];
TransformComponent* source_transform = data_scene->transforms.GetComponent(retarget.source);
if (source_transform != nullptr)
{
XMMATRIX dstRelativeMatrix = XMLoadFloat4x4(&retarget.dstRelativeMatrix);
XMMATRIX srcRelativeParentMatrix = XMLoadFloat4x4(&retarget.srcRelativeParentMatrix);
XMVECTOR S, T; // matrix decompose destinations
TransformComponent transform = *source_transform;
XMStoreFloat4(&transform.rotation_local, bR);
XMMATRIX localMatrix = dstRelativeMatrix * transform.GetLocalMatrix() * srcRelativeParentMatrix;
XMMatrixDecompose(&S, &bR, &T, localMatrix);
}
}
const XMVECTOR R = XMQuaternionSlerp(aR, bR, t);
if (!isRootBone)
{
// Not root motion bone.
XMStoreFloat4(&target_transform->rotation_local, R);
}
else
{
if (XMVector4Equal(animation.rootPrevRotation, animation.INVALID_VECTOR) || animation.end < animation.prevRotTimer)
{
// If root motion bone.
animation.rootPrevRotation = R;
}
// Assuming q1 and q2 are the two quaternions you want to subtract
// // Let's say you want to find the relative rotation from q1 to q2
XMMATRIX mat1 = XMMatrixRotationQuaternion(animation.rootPrevRotation);
XMMATRIX mat2 = XMMatrixRotationQuaternion(R);
// Compute the relative rotation matrix by multiplying the inverse of the first rotation
// by the second rotation
XMMATRIX relativeRotationMatrix = XMMatrixMultiply(XMMatrixTranspose(mat1), mat2);
// Extract the quaternion representing the relative rotation
XMVECTOR relativeRotationQuaternion = XMQuaternionRotationMatrix(relativeRotationMatrix);
// Store root motion offset
XMStoreFloat4(&animation.rootRotationOffset, relativeRotationQuaternion);
// Swap Y and Z Axis for Unknown reason
const float Y = animation.rootRotationOffset.y;
animation.rootRotationOffset.y = animation.rootRotationOffset.z;
animation.rootRotationOffset.z = Y;
// If root motion bone.
animation.rootPrevRotation = R;
animation.prevRotTimer = animation.timer;
}
}
break;
case AnimationComponent::AnimationChannel::Path::SCALE:
{
const XMVECTOR aS = XMLoadFloat3(&target_transform->scale_local);
XMVECTOR bS = XMLoadFloat3(&interpolator.f3);
if (channel.retargetIndex >= 0 && channel.retargetIndex < (int)animation.retargets.size())
{
// Retargeting transfer from source to destination:
const AnimationComponent::RetargetSourceData& retarget = animation.retargets[channel.retargetIndex];
TransformComponent* source_transform = data_scene->transforms.GetComponent(retarget.source);
if (source_transform != nullptr)
{
XMMATRIX dstRelativeMatrix = XMLoadFloat4x4(&retarget.dstRelativeMatrix);
XMMATRIX srcRelativeParentMatrix = XMLoadFloat4x4(&retarget.srcRelativeParentMatrix);
XMVECTOR R, T; // matrix decompose destinations
TransformComponent transform = *source_transform;
XMStoreFloat3(&transform.scale_local, bS);
XMMATRIX localMatrix = dstRelativeMatrix * transform.GetLocalMatrix() * srcRelativeParentMatrix;
XMMatrixDecompose(&bS, &R, &T, localMatrix);
}
}
const XMVECTOR S = XMVectorLerp(aS, bS, t);
XMStoreFloat3(&target_transform->scale_local, S);
}
break;
default:
break;
}
}
if (target_mesh != nullptr)
{
for (size_t j = 0; j < target_mesh->morph_targets.size(); ++j)
{
target_mesh->morph_targets[j].weight = wi::math::Lerp(target_mesh->morph_targets[j].weight, animation.morph_weights_temp[j], t);
}
}
if (target_light != nullptr)
{
switch (channel.path)
{
case AnimationComponent::AnimationChannel::Path::LIGHT_COLOR:
{
target_light->color = wi::math::Lerp(target_light->color, interpolator.f3, t);
}
break;
case AnimationComponent::AnimationChannel::Path::LIGHT_INTENSITY:
{
target_light->intensity = wi::math::Lerp(target_light->intensity, interpolator.f, t);
}
break;
case AnimationComponent::AnimationChannel::Path::LIGHT_RANGE:
{
target_light->range = wi::math::Lerp(target_light->range, interpolator.f, t);
}
break;
case AnimationComponent::AnimationChannel::Path::LIGHT_INNERCONE:
{
target_light->innerConeAngle = wi::math::Lerp(target_light->innerConeAngle, interpolator.f, t);
}
break;
case AnimationComponent::AnimationChannel::Path::LIGHT_OUTERCONE:
{
target_light->outerConeAngle = wi::math::Lerp(target_light->outerConeAngle, interpolator.f, t);
}
break;
default:
break;
}
}
if (target_sound != nullptr)
{
switch (channel.path)
{
case AnimationComponent::AnimationChannel::Path::SOUND_VOLUME:
{
target_sound->volume = wi::math::Lerp(target_sound->volume, interpolator.f, t);
}
break;
default:
break;
}
}
if (target_emitter != nullptr)
{
switch (channel.path)
{
case AnimationComponent::AnimationChannel::Path::EMITTER_EMITCOUNT:
{
target_emitter->count = wi::math::Lerp(target_emitter->count, interpolator.f, t);
}
break;
default:
break;
}
}
if (target_camera != nullptr)
{
switch (channel.path)
{
case AnimationComponent::AnimationChannel::Path::CAMERA_FOV:
{
target_camera->fov = wi::math::Lerp(target_camera->fov, interpolator.f, t);
}
break;
case AnimationComponent::AnimationChannel::Path::CAMERA_FOCAL_LENGTH:
{
target_camera->focal_length = wi::math::Lerp(target_camera->focal_length, interpolator.f, t);
}
break;
case AnimationComponent::AnimationChannel::Path::CAMERA_APERTURE_SIZE:
{
target_camera->aperture_size = wi::math::Lerp(target_camera->aperture_size, interpolator.f, t);
}
break;
case AnimationComponent::AnimationChannel::Path::CAMERA_APERTURE_SHAPE:
{
target_camera->aperture_shape = wi::math::Lerp(target_camera->aperture_shape, interpolator.f2, t);
}
break;
default:
break;
}
}
if (target_material != nullptr)
{
target_material->SetDirty();
switch (channel.path)
{
case AnimationComponent::AnimationChannel::Path::MATERIAL_COLOR:
{
target_material->baseColor = wi::math::Lerp(target_material->baseColor, interpolator.f4, t);
}
break;
case AnimationComponent::AnimationChannel::Path::MATERIAL_EMISSIVE:
{
target_material->emissiveColor = wi::math::Lerp(target_material->emissiveColor, interpolator.f4, t);
}
break;
case AnimationComponent::AnimationChannel::Path::MATERIAL_ROUGHNESS:
{
target_material->roughness = wi::math::Lerp(target_material->roughness, interpolator.f, t);
}
break;
case AnimationComponent::AnimationChannel::Path::MATERIAL_METALNESS:
{
target_material->metalness = wi::math::Lerp(target_material->metalness, interpolator.f, t);
}
break;
case AnimationComponent::AnimationChannel::Path::MATERIAL_REFLECTANCE:
{
target_material->reflectance = wi::math::Lerp(target_material->reflectance, interpolator.f, t);
}
break;
case AnimationComponent::AnimationChannel::Path::MATERIAL_TEXMULADD:
{
target_material->texMulAdd = wi::math::Lerp(target_material->texMulAdd, interpolator.f4, t);
}
break;
default:
break;
}
}
}
if (animation.IsLooped() && animation.timer > animation.end)
{
animation.timer = animation.start;
for (auto& channel : animation.channels)
{
channel.next_event = 0;
}
}
if (animation.IsPlaying())
{
animation.timer += dt * animation.speed;
}
}
});
wi::jobsystem::Wait(ctx);
wi::profiler::EndRange(range);
}
void Scene::RunTransformUpdateSystem(wi::jobsystem::context& ctx)
{
wi::jobsystem::Dispatch(ctx, (uint32_t)transforms.GetCount(), small_subtask_groupsize, [&](wi::jobsystem::JobArgs args) {
TransformComponent& transform = transforms[args.jobIndex];
transform.UpdateTransform();
});
}
void Scene::RunHierarchyUpdateSystem(wi::jobsystem::context& ctx)
{
wi::jobsystem::Dispatch(ctx, (uint32_t)hierarchy.GetCount(), small_subtask_groupsize, [&](wi::jobsystem::JobArgs args) {
HierarchyComponent& hier = hierarchy[args.jobIndex];
Entity entity = hierarchy.GetEntity(args.jobIndex);
TransformComponent* transform_child = transforms.GetComponent(entity);
XMMATRIX worldmatrix;
if (transform_child != nullptr)
{
worldmatrix = transform_child->GetLocalMatrix();
}
LayerComponent* layer_child = layers.GetComponent(entity);
if (layer_child != nullptr)
{
layer_child->propagationMask = ~0u; // clear propagation mask to full
}
if (transform_child == nullptr && layer_child == nullptr)
return;
Entity parentID = hier.parentID;
while (parentID != INVALID_ENTITY)
{
TransformComponent* transform_parent = transforms.GetComponent(parentID);
if (transform_child != nullptr && transform_parent != nullptr)
{
worldmatrix *= transform_parent->GetLocalMatrix();
}
LayerComponent* layer_parent = layers.GetComponent(parentID);
if (layer_child != nullptr && layer_parent != nullptr)
{
layer_child->propagationMask &= layer_parent->layerMask;
}
const HierarchyComponent* hier_recursive = hierarchy.GetComponent(parentID);
if (hier_recursive != nullptr)
{
parentID = hier_recursive->parentID;
}
else
{
parentID = INVALID_ENTITY;
}
}
if (transform_child != nullptr)
{
XMStoreFloat4x4(&transform_child->world, worldmatrix);
}
});
}
void Scene::RunExpressionUpdateSystem(wi::jobsystem::context& ctx)
{
for (size_t i = 0; i < expressions.GetCount(); ++i)
{
Entity entity = expressions.GetEntity(i);
ExpressionComponent& expression_mastering = expressions[i];
// Procedural blink:
expression_mastering.blink_timer += expression_mastering.blink_frequency * dt;
if (expression_mastering.blink_timer >= 1)
{
int blink = expression_mastering.presets[(int)ExpressionComponent::Preset::Blink];
if (blink >= 0 && blink < expression_mastering.expressions.size())
{
ExpressionComponent::Expression& expression = expression_mastering.expressions[blink];
expression_mastering.blink_count = std::max(1, expression_mastering.blink_count);
float one_blink_length = expression_mastering.blink_length * expression_mastering.blink_frequency;
float all_blink_length = one_blink_length * (float)expression_mastering.blink_count;
float blink_index = std::floor(wi::math::Lerp(0, (float)expression_mastering.blink_count, (expression_mastering.blink_timer - 1) / all_blink_length));
float blink_trim = 1 + one_blink_length * blink_index;
float blink_state = wi::math::InverseLerp(0, one_blink_length, expression_mastering.blink_timer - blink_trim);
if (blink_state < 0.5f)
{
// closing
expression.weight = wi::math::Lerp(0, 1, wi::math::saturate(blink_state * 2));
}
else
{
// opening
expression.weight = wi::math::Lerp(1, 0, wi::math::saturate((blink_state - 0.5f) * 2));
}
if (expression_mastering.blink_timer >= 1 + all_blink_length)
{
expression.weight = 0;
expression_mastering.blink_timer = -wi::random::GetRandom(0.0f, 1.0f);
}
expression.SetDirty();
}
}
// Procedural look:
if (expression_mastering.look_timer == 0)
{
// Roll new random look direction for next look away event:
float vertical = wi::random::GetRandom(-1.0f, 1.0f);
float horizontal = wi::random::GetRandom(-1.0f, 1.0f);
expression_mastering.look_weights[0] = wi::math::saturate(vertical);
expression_mastering.look_weights[1] = wi::math::saturate(-vertical);
expression_mastering.look_weights[2] = wi::math::saturate(horizontal);
expression_mastering.look_weights[3] = wi::math::saturate(-horizontal);
}
expression_mastering.look_timer += expression_mastering.look_frequency * dt;
if (expression_mastering.look_timer >= 1)
{
int looks[] = {
expression_mastering.presets[(int)ExpressionComponent::Preset::LookDown],
expression_mastering.presets[(int)ExpressionComponent::Preset::LookUp],
expression_mastering.presets[(int)ExpressionComponent::Preset::LookLeft],
expression_mastering.presets[(int)ExpressionComponent::Preset::LookRight],
};
for (int idx = 0; idx<arraysize(looks); ++idx)
{
int look = looks[idx];
const float weight = expression_mastering.look_weights[idx];
if (look >= 0 && look < expression_mastering.expressions.size())
{
ExpressionComponent::Expression& expression = expression_mastering.expressions[look];
float look_state = wi::math::InverseLerp(0, expression_mastering.look_length * expression_mastering.look_frequency, expression_mastering.look_timer - 1);
if (look_state < 0.25f)
{
expression.weight = wi::math::Lerp(0, weight, wi::math::saturate(look_state * 4));
}
else
{
expression.weight = wi::math::Lerp(weight, 0, wi::math::saturate((look_state - 0.75f) * 4));
}
expression.SetDirty();
}
}
if (expression_mastering.look_timer >= 1 + expression_mastering.look_length * expression_mastering.look_frequency)
{
expression_mastering.look_timer = -wi::random::GetRandom(0.0f, 1.0f);
}
}
// Talking animation based on sound:
const SoundComponent* sound = sounds.GetComponent(entity);
const bool voice_playing = sound != nullptr && sound->soundResource.IsValid() && sound->IsPlaying();
if(voice_playing || expression_mastering.IsForceTalkingEnabled())
{
ExpressionComponent::Preset unused_phonemes[4];
int next = 0;
for (int phoneme = (int)ExpressionComponent::Preset::Aa; phoneme <= (int)ExpressionComponent::Preset::Oh; phoneme++)
{
if (phoneme != (int)expression_mastering.talking_phoneme) // don't allow to select the current phoneme next
{
unused_phonemes[next++] = (ExpressionComponent::Preset)phoneme;
int mouth = expression_mastering.presets[(int)phoneme];
ExpressionComponent::Expression& expression = expression_mastering.expressions[mouth];
expression.weight = wi::math::Lerp(expression.weight, 0, 0.4f); // fade out unused
expression.SetDirty();
}
}
int mouth = expression_mastering.presets[(int)expression_mastering.talking_phoneme];
ExpressionComponent::Expression& expression = expression_mastering.expressions[mouth];
if (voice_playing)
{
// Take voice sample from audio:
wi::audio::SampleInfo info = wi::audio::GetSampleInfo(&sound->soundResource.GetSound());
uint32_t sample_frequency = info.sample_rate * info.channel_count;
uint64_t current_sample = wi::audio::GetTotalSamplesPlayed(&sound->soundinstance);
if (sound->IsLooped())
{
float total_time = float(current_sample) / float(info.sample_rate);
if (total_time > sound->soundinstance.loop_begin)
{
float loop_length = sound->soundinstance.loop_length > 0 ? sound->soundinstance.loop_length : (float(info.sample_count) / float(sample_frequency));
float loop_time = std::fmod(total_time - sound->soundinstance.loop_begin, loop_length);
current_sample = uint64_t(loop_time * info.sample_rate);
}
}
current_sample *= info.channel_count;
current_sample = std::min(current_sample, info.sample_count);
float voice = 0;
const int sample_count = 64;
for (int sam = 0; sam < sample_count; ++sam)
{
voice = std::max(voice, std::abs((float)info.samples[std::min(current_sample + sam, info.sample_count)] / 32768.0f));
}
const float strength = 0.4f;
if (voice > 0.1f)
{
expression.weight = wi::math::Lerp(expression.weight, 1, strength);
}
else
{
expression.weight = wi::math::Lerp(expression.weight, 0, strength);
}
}
else
{
float wave = std::sin(time * 30) * 0.5f + 0.5f;
expression.weight = wave;
}
float prev_slope = expression_mastering.talking_weight_prev - expression_mastering.talking_weight_prev_prev;
float curr_slope = expression.weight - expression_mastering.talking_weight_prev;
expression_mastering.talking_weight_prev_prev = expression_mastering.talking_weight_prev;
expression_mastering.talking_weight_prev = expression.weight;
if (prev_slope < 0 && curr_slope > 0)
{
// New phoneme when voice slope valley is detected:
expression_mastering.talking_phoneme = unused_phonemes[wi::random::GetRandom(0, (int)arraysize(unused_phonemes) - 1)];
}
expression.SetDirty();
}
else if (expression_mastering._flags & ExpressionComponent::TALKING_ENDED)
{
// When talking ended, smoothly blend out all phoneme expressions:
bool talking_active = false;
int phonemes[] = {
expression_mastering.presets[(int)ExpressionComponent::Preset::Aa],
expression_mastering.presets[(int)ExpressionComponent::Preset::Ih],
expression_mastering.presets[(int)ExpressionComponent::Preset::Ou],
expression_mastering.presets[(int)ExpressionComponent::Preset::Ee],
expression_mastering.presets[(int)ExpressionComponent::Preset::Oh],
};
for (auto& phoneme : phonemes)
{
if (phoneme < 0)
continue;
auto& expression = expression_mastering.expressions[phoneme];
expression.weight = wi::math::Lerp(expression.weight, 0, 0.4f);
expression.SetDirty();
if (expression.weight > 0)
talking_active = true;
}
if (!talking_active)
{
expression_mastering._flags &= ~ExpressionComponent::TALKING_ENDED;
}
}
float overrideMouthBlend = 0;
float overrideBlinkBlend = 0;
float overrideLookBlend = 0;
// Pass 1: reset targets that will be modified by expressions:
// Also accumulate override weights
for(ExpressionComponent::Expression& expression : expression_mastering.expressions)
{
if (expression.weight > 0)
{
const float blend = expression.IsBinary() ? 1 : expression.weight;
if (expression.override_mouth == ExpressionComponent::Override::Block)
{
overrideMouthBlend += 1;
}
if (expression.override_mouth == ExpressionComponent::Override::Blend)
{
overrideMouthBlend += blend;
}
if (expression.override_blink == ExpressionComponent::Override::Block)
{
overrideBlinkBlend += 1;
}
if (expression.override_blink == ExpressionComponent::Override::Blend)
{
overrideBlinkBlend += blend;
}
if (expression.override_look == ExpressionComponent::Override::Block)
{
overrideLookBlend += 1;
}
if (expression.override_look == ExpressionComponent::Override::Blend)
{
overrideLookBlend += blend;
}
}
if (!expression.IsDirty())
continue;
for (const ExpressionComponent::Expression::MorphTargetBinding& morph_target_binding : expression.morph_target_bindings)
{
MeshComponent* mesh = meshes.GetComponent(morph_target_binding.meshID);
if (mesh != nullptr && (int)mesh->morph_targets.size() > morph_target_binding.index)
{
MeshComponent::MorphTarget& morph_target = mesh->morph_targets[morph_target_binding.index];
if (morph_target.weight > 0)
{
morph_target.weight = 0;
}
}
}
}
// Override weights are factored in:
const int mouths[] = {
expression_mastering.presets[(int)ExpressionComponent::Preset::Aa],
expression_mastering.presets[(int)ExpressionComponent::Preset::Ih],
expression_mastering.presets[(int)ExpressionComponent::Preset::Ou],
expression_mastering.presets[(int)ExpressionComponent::Preset::Ee],
expression_mastering.presets[(int)ExpressionComponent::Preset::Oh],
};
for (int mouth : mouths)
{
if (mouth >= 0 && mouth < expression_mastering.expressions.size())
{
ExpressionComponent::Expression& expression = expression_mastering.expressions[mouth];
expression.weight *= 1 - wi::math::saturate(overrideMouthBlend);
}
}
const int blinks[] = {
expression_mastering.presets[(int)ExpressionComponent::Preset::Blink],
expression_mastering.presets[(int)ExpressionComponent::Preset::BlinkLeft],
expression_mastering.presets[(int)ExpressionComponent::Preset::BlinkRight],
};
for (int blink : blinks)
{
if (blink >= 0 && blink < expression_mastering.expressions.size())
{
ExpressionComponent::Expression& expression = expression_mastering.expressions[blink];
expression.weight *= 1 - wi::math::saturate(overrideBlinkBlend);
}
}
const int looks[] = {
expression_mastering.presets[(int)ExpressionComponent::Preset::LookUp],
expression_mastering.presets[(int)ExpressionComponent::Preset::LookDown],
expression_mastering.presets[(int)ExpressionComponent::Preset::LookLeft],
expression_mastering.presets[(int)ExpressionComponent::Preset::LookRight],
};
for (int look : looks)
{
if (look >= 0 && look < expression_mastering.expressions.size())
{
ExpressionComponent::Expression& expression = expression_mastering.expressions[look];
expression.weight *= 1 - wi::math::saturate(overrideLookBlend);
}
}
// Pass 2: apply expressions:
for (ExpressionComponent::Expression& expression : expression_mastering.expressions)
{
if (!expression.IsDirty())
continue;
expression.SetDirty(false);
const float blend = expression.IsBinary() ? (expression.weight > 0 ? 1 : 0) : expression.weight;
for (const ExpressionComponent::Expression::MorphTargetBinding& morph_target_binding : expression.morph_target_bindings)
{
MeshComponent* mesh = meshes.GetComponent(morph_target_binding.meshID);
if (mesh != nullptr && (int)mesh->morph_targets.size() > morph_target_binding.index)
{
MeshComponent::MorphTarget& morph_target = mesh->morph_targets[morph_target_binding.index];
morph_target.weight = wi::math::Lerp(morph_target.weight, morph_target_binding.weight, blend);
}
}
}
}
}
void Scene::RunProceduralAnimationUpdateSystem(wi::jobsystem::context& ctx)
{
if (dt <= 0)
return;
auto range = wi::profiler::BeginRangeCPU("Procedural Animations");
if (inverse_kinematics.GetCount() > 0 || humanoids.GetCount() > 0)
{
transforms_temp = transforms.GetComponentArray(); // make copy
}
bool recompute_hierarchy = false;
for (size_t i = 0; i < inverse_kinematics.GetCount(); ++i)
{
const InverseKinematicsComponent& ik = inverse_kinematics[i];
if (ik.IsDisabled())
{
continue;
}
Entity entity = inverse_kinematics.GetEntity(i);
size_t transform_index = transforms.GetIndex(entity);
size_t target_index = transforms.GetIndex(ik.target);
const HierarchyComponent* hier = hierarchy.GetComponent(entity);
if (transform_index == ~0ull || target_index == ~0ull || hier == nullptr)
{
continue;
}
TransformComponent& transform = transforms_temp[transform_index];
TransformComponent& target = transforms_temp[target_index];
const XMVECTOR target_pos = target.GetPositionV();
for (uint32_t iteration = 0; iteration < ik.iteration_count; ++iteration)
{
TransformComponent* stack[32] = {};
Entity parent_entity = hier->parentID;
TransformComponent* child_transform = &transform;
for (uint32_t chain = 0; chain < std::min(ik.chain_length, (uint32_t)arraysize(stack)); ++chain)
{
recompute_hierarchy = true; // any IK will trigger a full transform hierarchy recompute step at the end(**)
// stack stores all traversed chain links so far:
stack[chain] = child_transform;
// Compute required parent rotation that moves ik transform closer to target transform:
size_t parent_index = transforms.GetIndex(parent_entity);
if (parent_index == ~0ull)
continue;
TransformComponent& parent_transform = transforms_temp[parent_index];
const XMVECTOR parent_pos = parent_transform.GetPositionV();
const XMVECTOR dir_parent_to_ik = XMVector3Normalize(transform.GetPositionV() - parent_pos);
const XMVECTOR dir_parent_to_target = XMVector3Normalize(target_pos - parent_pos);
// Check if this transform is part of a humanoid and need some constraining:
bool constrain = false;
XMFLOAT3 constraint_min = XMFLOAT3(0, 0, 0);
XMFLOAT3 constraint_max = XMFLOAT3(0, 0, 0);
for (size_t humanoid_idx = 0; (humanoid_idx < humanoids.GetCount()) && !constrain; ++humanoid_idx)
{
const HumanoidComponent& humanoid = humanoids[humanoid_idx];
Entity humanoidEntity = humanoids.GetEntity(humanoid_idx);
const float facing = GetHumanoidDefaultFacing(humanoid, humanoidEntity);
int bone_type_idx = 0;
for (auto& bone : humanoid.bones)
{
if (bone == parent_entity)
{
switch ((HumanoidComponent::HumanoidBone)bone_type_idx)
{
default:
break;
case HumanoidComponent::HumanoidBone::LeftUpperLeg:
case HumanoidComponent::HumanoidBone::RightUpperLeg:
constrain = true;
constraint_min = XMFLOAT3(XM_PI * 0.6f, XM_PI * 0.1f, XM_PI * 0.1f);
constraint_max = XMFLOAT3(XM_PI * 0.1f, XM_PI * 0.1f, XM_PI * 0.1f);
break;
case HumanoidComponent::HumanoidBone::LeftLowerLeg:
case HumanoidComponent::HumanoidBone::RightLowerLeg:
constrain = true;
constraint_min = XMFLOAT3(0, 0, 0);
constraint_max = XMFLOAT3(XM_PI * 0.8f, 0, 0);
break;
}
}
if (constrain)
{
// Constraint swapping fixes for flipped model orientations:
if (facing < 0)
{
// Note: this is a fix for VRM 1.0 and Mixamo model
std::swap(constraint_min, constraint_max);
}
const TransformComponent* bone_transform = transforms.GetComponent(bone);
if (bone_transform != nullptr)
{
if (bone_transform->GetForward().z < 0)
{
// Note: this is a fix for FBX Mixamo models
std::swap(constraint_min, constraint_max);
}
}
break;
}
bone_type_idx++;
}
}
XMVECTOR Q;
if (constrain)
{
// Apply constrained rotation:
Q = XMQuaternionIdentity();
XMMATRIX W = XMLoadFloat4x4(&parent_transform.world);
const float iteration_count_rcp = 1.0f / (float)ik.iteration_count;
for (int axis_idx = 0; axis_idx < 3; ++axis_idx)
{
XMFLOAT3 axis_floats = XMFLOAT3(0, 0, 0);
((float*)&axis_floats)[axis_idx] = 1;
XMVECTOR axis = XMLoadFloat3(&axis_floats);
const float axis_min = ((float*)&constraint_min)[axis_idx] * iteration_count_rcp;
const float axis_max = ((float*)&constraint_max)[axis_idx] * iteration_count_rcp;
axis = XMVector3Normalize(XMVector3TransformNormal(axis, W));
const XMVECTOR projA = XMVector3Normalize(dir_parent_to_ik - axis * XMVector3Dot(axis, dir_parent_to_ik));
const XMVECTOR projB = XMVector3Normalize(dir_parent_to_target - axis * XMVector3Dot(axis, dir_parent_to_target));
float angle = XMVectorGetX(XMVector3AngleBetweenNormals(projA, projB));
if (XMVectorGetX(XMVector3Dot(XMVector3Cross(projA, projB), axis)) < 0)
{
angle = XM_2PI - std::min(angle, axis_min);
}
else
{
angle = std::min(angle, axis_max);
}
const XMVECTOR Q1 = XMQuaternionNormalize(XMQuaternionRotationNormal(axis, angle));
W = XMMatrixRotationQuaternion(Q1) * W;
Q = XMQuaternionMultiply(Q1, Q);
}
Q = XMQuaternionNormalize(Q);
}
else
{
// Simple shortest rotation without constraint:
const XMVECTOR axis = XMVector3Normalize(XMVector3Cross(dir_parent_to_ik, dir_parent_to_target));
const float angle = XMScalarACos(XMVectorGetX(XMVector3Dot(dir_parent_to_ik, dir_parent_to_target)));
Q = XMQuaternionNormalize(XMQuaternionRotationNormal(axis, angle));
}
// parent to world space:
parent_transform.ApplyTransform();
// rotate parent:
parent_transform.Rotate(Q);
parent_transform.UpdateTransform();
// parent back to local space (if parent has parent):
const HierarchyComponent* hier_parent = hierarchy.GetComponent(parent_entity);
if (hier_parent != nullptr)
{
Entity parent_of_parent_entity = hier_parent->parentID;
size_t parent_of_parent_index = transforms.GetIndex(parent_of_parent_entity);
if (parent_of_parent_index != ~0ull)
{
const TransformComponent* transform_parent_of_parent = &transforms_temp[parent_of_parent_index];
XMMATRIX parent_of_parent_inverse = XMMatrixInverse(nullptr, XMLoadFloat4x4(&transform_parent_of_parent->world));
parent_transform.MatrixTransform(parent_of_parent_inverse);
// Do not call UpdateTransform() here, to keep parent world matrix in world space!
}
}
// update chain from parent to children:
const TransformComponent* recurse_parent = &parent_transform;
for (int recurse_chain = (int)chain; recurse_chain >= 0; --recurse_chain)
{
stack[recurse_chain]->UpdateTransform_Parented(*recurse_parent);
recurse_parent = stack[recurse_chain];
}
if (hier_parent == nullptr)
{
// chain root reached, exit
break;
}
// move up in the chain by one:
child_transform = &parent_transform;
parent_entity = hier_parent->parentID;
assert(chain < (uint32_t)arraysize(stack) - 1); // if this is encountered, just extend stack array size
}
}
}
for (size_t i = 0; i < humanoids.GetCount(); ++i)
{
Entity humanoidEntity = humanoids.GetEntity(i);
HumanoidComponent& humanoid = humanoids[i];
// The head is always taken as reference frame transform even for the eyes:
// Note: taking eye reference frame transform for the eyes was causing issue with VRM 1.0 because eyes were rotated differently than head
const Entity headBone = humanoid.bones[size_t(HumanoidComponent::HumanoidBone::Head)];
if (headBone == INVALID_ENTITY)
continue;
const size_t headBoneIndex = transforms.GetIndex(headBone);
if (headBoneIndex == ~0ull)
continue;
const TransformComponent& head_transform = transforms_temp[headBoneIndex];
const XMVECTOR UP = XMVectorSet(0, 1, 0, 0);
const XMVECTOR SIDE = XMVectorSet(1, 0, 0, 0);
const XMVECTOR FORWARD = XMVectorSet(0, 0, GetHumanoidDefaultFacing(humanoid, humanoidEntity), 0);
struct LookAtSource
{
HumanoidComponent::HumanoidBone type;
XMFLOAT2* rotation_max;
float* rotation_speed;
XMFLOAT4* lookAtDeltaRotationState;
};
LookAtSource sources[] = {
{ HumanoidComponent::HumanoidBone::Head, &humanoid.head_rotation_max, &humanoid.head_rotation_speed, &humanoid.lookAtDeltaRotationState_Head },
{ HumanoidComponent::HumanoidBone::LeftEye, &humanoid.eye_rotation_max, &humanoid.eye_rotation_speed, &humanoid.lookAtDeltaRotationState_LeftEye },
{ HumanoidComponent::HumanoidBone::RightEye, &humanoid.eye_rotation_max, &humanoid.eye_rotation_speed, &humanoid.lookAtDeltaRotationState_RightEye },
};
for (auto& source : sources)
{
const Entity bone = humanoid.bones[size_t(source.type)];
if (bone == INVALID_ENTITY)
continue;
const size_t boneIndex = transforms.GetIndex(bone);
if (boneIndex == ~0ull)
continue;
if (boneIndex < transforms_temp.size())
{
recompute_hierarchy = true;
TransformComponent& transform = transforms_temp[boneIndex];
XMVECTOR Q = XMQuaternionIdentity();
if (humanoid.IsLookAtEnabled())
{
const HierarchyComponent* hier = hierarchy.GetComponent(bone);
size_t parent_index = hier == nullptr ? ~0ull : transforms.GetIndex(hier->parentID);
if (parent_index != ~0ull)
{
const TransformComponent& parent_transform = transforms_temp[parent_index];
transform.UpdateTransform_Parented(parent_transform);
}
const XMVECTOR P = transform.GetPositionV();
const XMMATRIX HeadW = XMLoadFloat4x4(&head_transform.world); // take it inside iteration loop!
const XMMATRIX HeadInverseW = XMMatrixInverse(nullptr, HeadW); // take it inside iteration loop!
const XMVECTOR TARGET = XMVector3Normalize(XMVector3TransformNormal(XMLoadFloat3(&humanoid.lookAt) - P, HeadInverseW));
const XMVECTOR TARGET_HORIZONTAL = XMVector3Normalize(XMVectorSetY(TARGET, 0));
const XMVECTOR TARGET_VERTICAL = XMVector3Normalize(XMVectorSetX(TARGET, 0) + FORWARD);
const float angle_horizontal = wi::math::GetAngle(FORWARD, TARGET_HORIZONTAL, UP, source.rotation_max->x);
const float angle_vertical = wi::math::GetAngle(FORWARD, TARGET_VERTICAL, SIDE, source.rotation_max->y);
Q = XMQuaternionNormalize(XMQuaternionRotationRollPitchYaw(angle_vertical, angle_horizontal, 0));
#if 0
wi::renderer::RenderableLine line;
line.color_start = XMFLOAT4(0, 0, 1, 1);
line.color_end = XMFLOAT4(0, 1, 0, 1);
XMVECTOR E = P + FORWARD;
XMStoreFloat3(&line.start, P);
XMStoreFloat3(&line.end, E);
wi::renderer::DrawLine(line);
line.color_end = XMFLOAT4(1, 0, 0, 1);
E = P + TARGET;
XMStoreFloat3(&line.end, E);
wi::renderer::DrawLine(line);
line.color_start = line.color_end = XMFLOAT4(1, 0, 1, 1);
E = P + UP;
XMStoreFloat3(&line.end, E);
wi::renderer::DrawLine(line);
line.color_start = line.color_end = XMFLOAT4(1, 1, 0, 1);
E = P + SIDE;
XMStoreFloat3(&line.end, E);
wi::renderer::DrawLine(line);
std::string text = "angle_horizontal = " + std::to_string(angle_horizontal);
text += "\nangle_vertical = " + std::to_string(angle_vertical);
wi::renderer::DebugTextParams textparams;
textparams.flags |= wi::renderer::DebugTextParams::CAMERA_FACING;
textparams.flags |= wi::renderer::DebugTextParams::CAMERA_SCALING;
textparams.position = humanoid.lookAt;
textparams.scaling = 0.8f;
wi::renderer::DrawDebugText(text.c_str(), textparams);
#endif
}
Q = XMQuaternionSlerp(XMLoadFloat4(source.lookAtDeltaRotationState), Q, *source.rotation_speed);
Q = XMQuaternionNormalize(Q);
XMStoreFloat4(source.lookAtDeltaRotationState, Q);
// Local space and world space updated separately:
transform.Rotate(Q); // local space for having hierarchy recompute at the end
XMMATRIX W = XMLoadFloat4x4(&transform.world);
W = XMMatrixRotationQuaternion(Q) * W;
XMStoreFloat4x4(&transform.world, W); // world space to have immediate feedback from parent to child (head -> eyes)
}
}
}
if (recompute_hierarchy)
{
wi::jobsystem::Dispatch(ctx, (uint32_t)hierarchy.GetCount(), small_subtask_groupsize, [&](wi::jobsystem::JobArgs args) {
HierarchyComponent& hier = hierarchy[args.jobIndex];
Entity entity = hierarchy.GetEntity(args.jobIndex);
size_t child_index = transforms.GetIndex(entity);
if (child_index == ~0ull)
return;
TransformComponent& transform_child = transforms_temp[child_index];
XMMATRIX worldmatrix = transform_child.GetLocalMatrix();
Entity parentID = hier.parentID;
while (parentID != INVALID_ENTITY)
{
size_t parent_index = transforms.GetIndex(parentID);
if (parent_index == ~0ull)
break;
TransformComponent& transform_parent = transforms_temp[parent_index];
worldmatrix *= transform_parent.GetLocalMatrix();
const HierarchyComponent* hier_recursive = hierarchy.GetComponent(parentID);
if (hier_recursive != nullptr)
{
parentID = hier_recursive->parentID;
}
else
{
parentID = INVALID_ENTITY;
}
}
// Now the real (not temp) transform world matrix is updated:
XMStoreFloat4x4(&transforms[child_index].world, worldmatrix);
});
wi::jobsystem::Wait(ctx);
}
// Colliders:
collider_allocator_cpu.store(0u);
collider_allocator_gpu.store(0u);
collider_deinterleaved_data.reserve(
sizeof(wi::primitive::AABB) * colliders.GetCount() +
sizeof(ColliderComponent) * colliders.GetCount() +
sizeof(ColliderComponent) * colliders.GetCount()
);
aabb_colliders_cpu = (wi::primitive::AABB*)collider_deinterleaved_data.data();
colliders_cpu = (ColliderComponent*)(aabb_colliders_cpu + colliders.GetCount());
colliders_gpu = colliders_cpu + colliders.GetCount();
wi::jobsystem::Dispatch(ctx, (uint32_t)colliders.GetCount(), small_subtask_groupsize, [&](wi::jobsystem::JobArgs args) {
ColliderComponent& collider = colliders[args.jobIndex];
Entity entity = colliders.GetEntity(args.jobIndex);
const TransformComponent* transform = transforms.GetComponent(entity);
if (transform == nullptr)
return;
XMFLOAT3 scale = transform->GetScale();
collider.sphere.radius = collider.radius * std::max(scale.x, std::max(scale.y, scale.z));
collider.capsule.radius = collider.sphere.radius;
XMMATRIX W = XMLoadFloat4x4(&transform->world);
XMVECTOR offset = XMLoadFloat3(&collider.offset);
XMVECTOR tail = XMLoadFloat3(&collider.tail);
offset = XMVector3Transform(offset, W);
tail = XMVector3Transform(tail, W);
XMStoreFloat3(&collider.sphere.center, offset);
XMVECTOR N = XMVector3Normalize(offset - tail);
offset += N * collider.capsule.radius;
tail -= N * collider.capsule.radius;
XMStoreFloat3(&collider.capsule.base, offset);
XMStoreFloat3(&collider.capsule.tip, tail);
AABB aabb;
switch (collider.shape)
{
default:
case ColliderComponent::Shape::Sphere:
aabb.createFromHalfWidth(collider.sphere.center, XMFLOAT3(collider.sphere.radius, collider.sphere.radius, collider.sphere.radius));
break;
case ColliderComponent::Shape::Capsule:
aabb = collider.capsule.getAABB();
break;
case ColliderComponent::Shape::Plane:
{
collider.plane.origin = collider.sphere.center;
XMVECTOR N = XMVectorSet(0, 1, 0, 0);
N = XMVector3Normalize(XMVector3TransformNormal(N, W));
XMStoreFloat3(&collider.plane.normal, N);
aabb.createFromHalfWidth(XMFLOAT3(0, 0, 0), XMFLOAT3(1, 1, 1));
XMMATRIX PLANE = XMMatrixScaling(collider.radius, 1, collider.radius);
PLANE = PLANE * XMMatrixTranslationFromVector(XMLoadFloat3(&collider.offset));
PLANE = PLANE * W;
aabb = aabb.transform(PLANE);
PLANE = XMMatrixInverse(nullptr, PLANE);
XMStoreFloat4x4(&collider.plane.projection, PLANE);
}
break;
}
const LayerComponent* layer = layers.GetComponent(entity);
if (layer != nullptr)
{
collider.layerMask = layer->GetLayerMask();
}
if (collider.IsCPUEnabled())
{
uint32_t index = collider_allocator_cpu.fetch_add(1u);
colliders_cpu[index] = collider;
aabb_colliders_cpu[index] = aabb;
}
if (collider.IsGPUEnabled())
{
uint32_t index = collider_allocator_gpu.fetch_add(1u);
colliders_gpu[index] = collider;
}
});
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:
wi::jobsystem::Wait(spring_dependency_scan_workload);
wi::jobsystem::Dispatch(ctx, (uint32_t)spring_queues.size(), 1, [this](wi::jobsystem::JobArgs args){
UpdateSpringsTopDownRecursive(nullptr, *spring_queues[args.jobIndex]);
});
wi::jobsystem::Wait(ctx);
wi::profiler::EndRange(range);
}
void Scene::RunArmatureUpdateSystem(wi::jobsystem::context& ctx)
{
wi::jobsystem::Dispatch(ctx, (uint32_t)armatures.GetCount(), 1, [&](wi::jobsystem::JobArgs args) {
ArmatureComponent& armature = armatures[args.jobIndex];
Entity entity = armatures.GetEntity(args.jobIndex);
if (!transforms.Contains(entity))
return;
const TransformComponent& transform = *transforms.GetComponent(entity);
// The transform world matrices are in world space, but skinning needs them in armature-local space,
// so that the skin is reusable for instanced meshes.
// We remove the armature's world matrix from the bone world matrix to obtain the bone local transform
// These local bone matrices will only be used for skinning, the actual transform components for the bones
// remain unchanged.
//
// This is useful for an other thing too:
// If a whole transform tree is transformed by some parent (even gltf import does that to convert from RH to LH space)
// then the inverseBindMatrices are not reflected in that because they are not contained in the hierarchy system.
// But this will correct them too.
XMMATRIX R = XMMatrixInverse(nullptr, XMLoadFloat4x4(&transform.world));
armature.gpuBoneOffset = skinningAllocator.fetch_add(uint32_t(armature.boneCollection.size() * sizeof(ShaderTransform)));
ShaderTransform* gpu_dst = (ShaderTransform*)((uint8_t*)skinningDataMapped + armature.gpuBoneOffset);
if (armature.boneData.size() != armature.boneCollection.size())
{
armature.boneData.resize(armature.boneCollection.size());
}
XMFLOAT3 _min = XMFLOAT3(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
XMFLOAT3 _max = XMFLOAT3(std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest());
uint32_t boneIndex = 0;
for (Entity boneEntity : armature.boneCollection)
{
const TransformComponent* bone = transforms.GetComponent(boneEntity);
if (bone == nullptr)
continue;
XMMATRIX B = XMLoadFloat4x4(&armature.inverseBindMatrices[boneIndex]);
XMMATRIX W = XMLoadFloat4x4(&bone->world);
XMMATRIX M = B * W * R;
XMFLOAT4X4 mat;
XMStoreFloat4x4(&mat, M);
ShaderTransform& shadertransform = armature.boneData[boneIndex];
shadertransform.Create(mat);
if (skinningDataMapped != nullptr)
{
std::memcpy(gpu_dst + boneIndex, &shadertransform, sizeof(shadertransform));
}
const float bone_radius = 1;
XMFLOAT3 bonepos = bone->GetPosition();
AABB boneAABB;
boneAABB.createFromHalfWidth(bonepos, XMFLOAT3(bone_radius, bone_radius, bone_radius));
_min = wi::math::Min(_min, boneAABB._min);
_max = wi::math::Max(_max, boneAABB._max);
boneIndex++;
}
armature.aabb = AABB(_min, _max);
});
}
void Scene::RunMeshUpdateSystem(wi::jobsystem::context& ctx)
{
wi::jobsystem::Dispatch(ctx, (uint32_t)meshes.GetCount(), small_subtask_groupsize, [&](wi::jobsystem::JobArgs args) {
Entity entity = meshes.GetEntity(args.jobIndex);
MeshComponent& mesh = meshes[args.jobIndex];
if (!mesh.streamoutBuffer.IsValid())
{
const SoftBodyPhysicsComponent* softbody = softbodies.GetComponent(entity);
if (softbody != nullptr && wi::physics::IsEnabled())
{
mesh.CreateStreamoutRenderData();
}
}
if (mesh.so_pos.IsValid() && mesh.so_pre.IsValid())
{
std::swap(mesh.so_pos, mesh.so_pre);
}
mesh._flags &= ~MeshComponent::TLAS_FORCE_DOUBLE_SIDED;
mesh.active_morph_count = 0;
if (skinningDataMapped != nullptr && !mesh.morph_targets.empty())
{
mesh.morphGPUOffset = skinningAllocator.fetch_add(uint32_t(mesh.morph_targets.size() * sizeof(MorphTargetGPU)));
MorphTargetGPU* gpu_dst = (MorphTargetGPU*)((uint8_t*)skinningDataMapped + mesh.morphGPUOffset);
for (const MeshComponent::MorphTarget& morph : mesh.morph_targets)
{
if (morph.weight > 0)
{
MorphTargetGPU morph_target_gpu = {};
morph_target_gpu.weight = morph.weight;
morph_target_gpu.offset_pos = (uint)morph.offset_pos;
morph_target_gpu.offset_nor = (uint)morph.offset_nor;
morph_target_gpu.offset_tan = ~0u;
std::memcpy(gpu_dst + mesh.active_morph_count, &morph_target_gpu, sizeof(morph_target_gpu));
mesh.active_morph_count++;
}
}
}
if (geometryArrayMapped != nullptr)
{
ShaderGeometry geometry;
geometry.init();
geometry.ib = mesh.ib.descriptor_srv;
if (mesh.so_pos.IsValid())
{
geometry.vb_pos_wind = mesh.so_pos.descriptor_srv;
}
else
{
geometry.vb_pos_wind = mesh.vb_pos_wind.descriptor_srv;
}
if (mesh.so_nor.IsValid())
{
geometry.vb_nor = mesh.so_nor.descriptor_srv;
}
else
{
geometry.vb_nor = mesh.vb_nor.descriptor_srv;
}
if (mesh.so_tan.IsValid())
{
geometry.vb_tan = mesh.so_tan.descriptor_srv;
}
else
{
geometry.vb_tan = mesh.vb_tan.descriptor_srv;
}
geometry.vb_col = mesh.vb_col.descriptor_srv;
geometry.vb_uvs = mesh.vb_uvs.descriptor_srv;
geometry.vb_atl = mesh.vb_atl.descriptor_srv;
geometry.vb_pre = mesh.so_pre.descriptor_srv;
geometry.aabb_min = mesh.aabb._min;
geometry.aabb_max = mesh.aabb._max;
geometry.tessellation_factor = mesh.tessellationFactor;
geometry.uv_range_min = mesh.uv_range_min;
geometry.uv_range_max = mesh.uv_range_max;
const ImpostorComponent* impostor = impostors.GetComponent(entity);
if (impostor != nullptr && impostor->textureIndex >= 0)
{
geometry.impostorSliceOffset = impostor->textureIndex * impostorCaptureAngles * 3;
}
if (mesh.IsDoubleSided())
{
geometry.flags |= SHADERMESH_FLAG_DOUBLE_SIDED;
}
mesh.meshletCount = 0;
uint32_t subsetIndex = 0;
for (auto& subset : mesh.subsets)
{
const MaterialComponent* material = materials.GetComponent(subset.materialID);
if (material != nullptr)
{
subset.materialIndex = (uint32_t)materials.GetIndex(subset.materialID);
}
else
{
subset.materialIndex = 0;
}
geometry.indexOffset = subset.indexOffset;
geometry.indexCount = subset.indexCount;
geometry.materialIndex = subset.materialIndex;
geometry.meshletOffset = mesh.meshletCount;
geometry.meshletCount = triangle_count_to_meshlet_count(subset.indexCount / 3u);
mesh.meshletCount += geometry.meshletCount;
std::memcpy(geometryArrayMapped + mesh.geometryOffset + subsetIndex, &geometry, sizeof(geometry));
subsetIndex++;
}
}
if (TLAS_instancesMapped != nullptr) // check TLAS, to know if we need to care about BLAS
{
if (mesh.BLASes.empty() || !mesh.BLASes[0].IsValid())
{
mesh.CreateRaytracingRenderData();
}
const uint32_t lod_count = mesh.GetLODCount();
assert(uint32_t(mesh.BLASes.size()) == lod_count);
for (uint32_t lod = 0; lod < lod_count; ++lod)
{
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 (materials.GetCount() <= subset.materialIndex)
continue;
const MaterialComponent& material = materials[subset.materialIndex];
const uint32_t geometry_index = subsetIndex - first_subset;
auto& geometry = mesh.BLASes[lod].desc.bottom_level.geometries[geometry_index];
uint32_t flags = geometry.flags;
if (material.IsAlphaTestEnabled() || (material.GetFilterMask() & FILTER_TRANSPARENT) || !material.IsCastingShadow())
{
geometry.flags &= ~RaytracingAccelerationStructureDesc::BottomLevel::Geometry::FLAG_OPAQUE;
}
else
{
geometry.flags = RaytracingAccelerationStructureDesc::BottomLevel::Geometry::FLAG_OPAQUE;
}
if (flags != geometry.flags || mesh.active_morph_count > 0)
{
mesh.BLAS_state = MeshComponent::BLAS_STATE_NEEDS_REBUILD;
}
if (mesh.streamoutBuffer.IsValid())
{
mesh.BLAS_state = MeshComponent::BLAS_STATE_NEEDS_REBUILD;
geometry.triangles.vertex_buffer = mesh.streamoutBuffer;
geometry.triangles.vertex_byte_offset = mesh.so_pos.offset;
}
if (material.IsDoubleSided())
{
mesh._flags |= MeshComponent::TLAS_FORCE_DOUBLE_SIDED;
}
}
}
}
});
}
void Scene::RunMaterialUpdateSystem(wi::jobsystem::context& ctx)
{
wi::jobsystem::Dispatch(ctx, (uint32_t)materials.GetCount(), small_subtask_groupsize, [&](wi::jobsystem::JobArgs args) {
MaterialComponent& material = materials[args.jobIndex];
Entity entity = materials.GetEntity(args.jobIndex);
const LayerComponent* layer = layers.GetComponent(entity);
if (layer != nullptr)
{
material.layerMask = layer->layerMask;
}
material.texAnimElapsedTime += dt * material.texAnimFrameRate;
if (material.texAnimElapsedTime >= 1.0f)
{
material.texMulAdd.z = fmodf(material.texMulAdd.z + material.texAnimDirection.x, 1);
material.texMulAdd.w = fmodf(material.texMulAdd.w + material.texAnimDirection.y, 1);
material.texAnimElapsedTime = 0.0f;
material.SetDirty();
}
material.engineStencilRef = STENCILREF_DEFAULT;
if (material.IsCustomShader())
{
if (material.IsOutlineEnabled())
{
material.engineStencilRef = STENCILREF_CUSTOMSHADER_OUTLINE;
}
else
{
material.engineStencilRef = STENCILREF_CUSTOMSHADER;
}
}
else if (material.IsOutlineEnabled())
{
material.engineStencilRef = STENCILREF_OUTLINE;
}
if (material.IsDirty())
{
material.SetDirty(false);
}
material.WriteShaderMaterial(materialArrayMapped + args.jobIndex);
VideoComponent* video = videos.GetComponent(entity);
if (video != nullptr)
{
// Video attachment will overwrite texture slots on shader side:
int descriptor = GetDevice()->GetDescriptorIndex(&video->videoinstance.output.texture, SubresourceType::SRV, video->videoinstance.output.subresource_srgb);
material.WriteShaderTextureSlot(materialArrayMapped + args.jobIndex, BASECOLORMAP, descriptor);
material.WriteShaderTextureSlot(materialArrayMapped + args.jobIndex, EMISSIVEMAP, descriptor);
}
});
}
void Scene::RunImpostorUpdateSystem(wi::jobsystem::context& ctx)
{
if (dt == 0)
return;
if (impostors.GetCount() > 0 && !impostorArray.IsValid())
{
GraphicsDevice* device = wi::graphics::GetDevice();
TextureDesc desc;
desc.width = impostorTextureDim;
desc.height = impostorTextureDim;
desc.sample_count = 8;
desc.bind_flags = BindFlag::DEPTH_STENCIL;
desc.format = Format::D16_UNORM;
desc.layout = ResourceState::DEPTHSTENCIL;
desc.misc_flags = ResourceMiscFlag::TRANSIENT_ATTACHMENT;
device->CreateTexture(&desc, nullptr, &impostorDepthStencil);
device->SetName(&impostorDepthStencil, "impostorDepthStencil");
desc.bind_flags = BindFlag::RENDER_TARGET;
desc.layout = ResourceState::RENDERTARGET;
desc.misc_flags = ResourceMiscFlag::TRANSIENT_ATTACHMENT;
desc.format = Format::R8G8B8A8_UNORM;
device->CreateTexture(&desc, nullptr, &impostorRenderTarget_Albedo_MSAA);
device->SetName(&impostorRenderTarget_Albedo_MSAA, "impostorRenderTarget_Albedo_MSAA");
desc.format = Format::R11G11B10_FLOAT;
device->CreateTexture(&desc, nullptr, &impostorRenderTarget_Normal_MSAA);
device->SetName(&impostorRenderTarget_Normal_MSAA, "impostorRenderTarget_Normal_MSAA");
desc.format = Format::R8G8B8A8_UNORM;
device->CreateTexture(&desc, nullptr, &impostorRenderTarget_Surface_MSAA);
device->SetName(&impostorRenderTarget_Surface_MSAA, "impostorRenderTarget_Surface_MSAA");
desc.sample_count = 1;
desc.misc_flags = ResourceMiscFlag::NONE;
desc.layout = ResourceState::SHADER_RESOURCE;
desc.bind_flags = BindFlag::SHADER_RESOURCE | BindFlag::RENDER_TARGET; // Note: RenderTarget required for MSAA resolve dest [PS5]
desc.format = Format::R8G8B8A8_UNORM;
device->CreateTexture(&desc, nullptr, &impostorRenderTarget_Albedo);
device->SetName(&impostorRenderTarget_Albedo, "impostorRenderTarget_Albedo");
desc.format = Format::R11G11B10_FLOAT;
device->CreateTexture(&desc, nullptr, &impostorRenderTarget_Normal);
device->SetName(&impostorRenderTarget_Normal, "impostorRenderTarget_Normal");
desc.format = Format::R8G8B8A8_UNORM;
device->CreateTexture(&desc, nullptr, &impostorRenderTarget_Surface);
device->SetName(&impostorRenderTarget_Surface, "impostorRenderTarget_Surface");
desc.format = Format::BC3_UNORM;
desc.bind_flags = BindFlag::SHADER_RESOURCE;
desc.layout = ResourceState::SHADER_RESOURCE;
desc.misc_flags = ResourceMiscFlag::NONE;
desc.array_size = maxImpostorCount * impostorCaptureAngles * 3;
device->CreateTexture(&desc, nullptr, &impostorArray);
device->SetName(&impostorArray, "impostorArray");
std::string info;
info += "Created impostor array with " + std::to_string(maxImpostorCount) + " max impostors";
info += "\n\tResolution (width * height * angles * properties * capacity) = " + std::to_string(impostorTextureDim) + " * " + std::to_string(impostorTextureDim) + " * " + std::to_string(impostorCaptureAngles) + " * 3 * " + std::to_string(maxImpostorCount);
info += "\n\tRender Sample count = " + std::to_string(impostorRenderTarget_Albedo_MSAA.desc.sample_count);
info += "\n\tRender Format Albedo = ";
info += GetFormatString(impostorRenderTarget_Albedo.desc.format);
info += "\n\tRender Format Normal = ";
info += GetFormatString(impostorRenderTarget_Normal.desc.format);
info += "\n\tRender Format Surface = ";
info += GetFormatString(impostorRenderTarget_Surface.desc.format);
info += "\n\tDepth Format = ";
info += GetFormatString(impostorDepthStencil.desc.format);
info += "\n\tSampled Format = ";
info += GetFormatString(impostorArray.desc.format);
size_t total_size = 0;
total_size += ComputeTextureMemorySizeInBytes(impostorArray.desc);
total_size += ComputeTextureMemorySizeInBytes(impostorDepthStencil.desc);
total_size += ComputeTextureMemorySizeInBytes(impostorRenderTarget_Albedo.desc);
total_size += ComputeTextureMemorySizeInBytes(impostorRenderTarget_Surface.desc);
total_size += ComputeTextureMemorySizeInBytes(impostorRenderTarget_Normal.desc);
total_size += ComputeTextureMemorySizeInBytes(impostorRenderTarget_Albedo_MSAA.desc);
total_size += ComputeTextureMemorySizeInBytes(impostorRenderTarget_Surface_MSAA.desc);
total_size += ComputeTextureMemorySizeInBytes(impostorRenderTarget_Normal_MSAA.desc);
info += "\n\tMemory = " + wi::helper::GetMemorySizeText(total_size) + "\n";
wi::backlog::post(info);
}
// reconstruct impostor array status:
bool impostorTaken[maxImpostorCount] = {};
for (size_t i = 0; i < impostors.GetCount(); ++i)
{
ImpostorComponent& impostor = impostors[i];
if (impostor.textureIndex >= 0 && impostor.textureIndex < maxImpostorCount)
{
impostorTaken[impostor.textureIndex] = true;
}
else
{
impostor.textureIndex = -1;
}
}
for (size_t i = 0; i < impostors.GetCount(); ++i)
{
ImpostorComponent& impostor = impostors[i];
if (impostor.IsDirty())
{
impostor.SetDirty(false);
impostor.render_dirty = true;
}
if (impostor.render_dirty && impostor.textureIndex < 0)
{
// need to take a free impostor texture slot:
for (int i = 0; i < arraysize(impostorTaken); ++i)
{
if (impostorTaken[i] == false)
{
impostorTaken[i] = true;
impostor.textureIndex = i;
break;
}
}
}
}
if (impostors.GetCount() > 0)
{
ShaderMaterial material;
material.init();
material.shaderType = ~0u;
std::memcpy(materialArrayMapped + impostorMaterialOffset, &material, sizeof(material));
ShaderGeometry geometry;
geometry.init();
geometry.meshletCount = triangle_count_to_meshlet_count(uint32_t(objects.GetCount()) * 2);
geometry.meshletOffset = 0; // local meshlet offset
geometry.ib = impostor_ib_format == Format::R32_UINT ? impostor_ib32.descriptor_srv : impostor_ib16.descriptor_srv;
geometry.vb_pos_wind = impostor_vb_pos.descriptor_srv;
geometry.vb_nor = impostor_vb_nor.descriptor_srv;
geometry.materialIndex = impostorMaterialOffset;
std::memcpy(geometryArrayMapped + impostorGeometryOffset, &geometry, sizeof(geometry));
ShaderMeshInstance inst;
inst.init();
inst.geometryOffset = impostorGeometryOffset;
inst.geometryCount = 1;
inst.baseGeometryOffset = inst.geometryOffset;
inst.baseGeometryCount = inst.geometryCount;
inst.meshletOffset = meshletAllocator.fetch_add(geometry.meshletCount); // global meshlet offset
std::memcpy(instanceArrayMapped + impostorInstanceOffset, &inst, sizeof(inst));
}
}
void Scene::RunObjectUpdateSystem(wi::jobsystem::context& ctx)
{
aabb_objects.resize(objects.GetCount());
matrix_objects.resize(objects.GetCount());
matrix_objects_prev.resize(objects.GetCount());
occlusion_results_objects.resize(objects.GetCount());
meshletAllocator.store(0u);
parallel_bounds.clear();
parallel_bounds.resize((size_t)wi::jobsystem::DispatchGroupCount((uint32_t)objects.GetCount(), small_subtask_groupsize));
wi::jobsystem::Dispatch(ctx, (uint32_t)objects.GetCount(), small_subtask_groupsize, [&](wi::jobsystem::JobArgs args) {
Entity entity = objects.GetEntity(args.jobIndex);
ObjectComponent& object = objects[args.jobIndex];
AABB& aabb = aabb_objects[args.jobIndex];
// Update occlusion culling status:
OcclusionResult& occlusion_result = occlusion_results_objects[args.jobIndex];
if (!wi::renderer::GetFreezeCullingCameraEnabled())
{
occlusion_result.occlusionHistory <<= 1u; // advance history by 1 frame
int query_id = occlusion_result.occlusionQueries[queryheap_idx];
if (queryResultBuffer[queryheap_idx].mapped_data != nullptr && query_id >= 0)
{
uint64_t visible = ((uint64_t*)queryResultBuffer[queryheap_idx].mapped_data)[query_id];
if (visible)
{
occlusion_result.occlusionHistory |= 1; // visible
}
}
else
{
occlusion_result.occlusionHistory |= 1; // visible
}
}
occlusion_result.occlusionQueries[queryheap_idx] = -1; // invalidate query
const LayerComponent* layer = layers.GetComponent(entity);
uint32_t layerMask;
if (layer == nullptr)
{
layerMask = ~0;
}
else
{
layerMask = layer->GetLayerMask();
}
aabb = AABB();
object.filterMaskDynamic = 0;
object.sort_bits = {};
object.SetDynamic(false);
object.SetRequestPlanarReflection(false);
object.fadeDistance = object.draw_distance;
if (object.meshID != INVALID_ENTITY && meshes.Contains(object.meshID) && transforms.Contains(entity))
{
// These will only be valid for a single frame:
object.mesh_index = (uint32_t)meshes.GetIndex(object.meshID);
const MeshComponent& mesh = meshes[object.mesh_index];
const TransformComponent& transform = *transforms.GetComponent(entity);
XMMATRIX W = XMLoadFloat4x4(&transform.world);
aabb = mesh.aabb.transform(W);
if (mesh.IsSkinned() || mesh.IsDynamic())
{
object.SetDynamic(true);
const ArmatureComponent* armature = armatures.GetComponent(mesh.armatureID);
if (armature != nullptr)
{
aabb = AABB::Merge(aabb, armature->aabb);
}
}
ImpostorComponent* impostor = impostors.GetComponent(object.meshID);
if (impostor != nullptr)
{
object.fadeDistance = std::min(object.fadeDistance, impostor->swapInDistance);
}
SoftBodyPhysicsComponent* softbody = softbodies.GetComponent(object.meshID);
if (softbody != nullptr && mesh.streamoutBuffer.IsValid())
{
if (wi::physics::IsEnabled())
{
// this will be registered as soft body in the next physics update
softbody->_flags |= SoftBodyPhysicsComponent::SAFE_TO_REGISTER;
// soft body manipulated with the object matrix
softbody->worldMatrix = transform.world;
if (softbody->graphicsToPhysicsVertexMapping.empty())
{
softbody->CreateFromMesh(mesh);
}
}
// simulation aabb will be used for soft bodies
aabb = softbody->aabb;
// soft bodies have no transform, their vertices are simulated in world space
W = XMMatrixIdentity();
}
object.center = aabb.getCenter();
object.radius = aabb.getRadius();
// LOD select:
if (mesh.subsets_per_lod > 0)
{
const float distsq = wi::math::DistanceSquared(camera.Eye, object.center);
const float radius = object.radius;
const float radiussq = radius * radius;
if (distsq < radiussq)
{
object.lod = 0;
}
else
{
const float dist = std::sqrt(distsq);
const float dist_to_sphere = dist - radius;
object.lod = uint32_t(dist_to_sphere * object.lod_distance_multiplier);
object.lod = std::min(object.lod, mesh.GetLODCount() - 1);
}
}
union SortBits
{
struct
{
uint32_t shadertype : MaterialComponent::SHADERTYPE_COUNT;
uint32_t blendmode : wi::enums::BLENDMODE_COUNT;
uint32_t doublesided : 1; // bool
uint32_t tessellation : 1; // bool
uint32_t alphatest : 1; // bool
uint32_t customshader : 8;
uint32_t sort_priority : 4;
} bits;
uint32_t value;
} sort_bits;
static_assert(sizeof(SortBits) == sizeof(uint32_t));
sort_bits.bits.tessellation = mesh.GetTessellationFactor() > 0;
sort_bits.bits.doublesided = mesh.IsDoubleSided();
sort_bits.bits.sort_priority = object.sort_priority;
uint32_t first_subset = 0;
uint32_t last_subset = 0;
mesh.GetLODSubsetRange(object.lod, first_subset, last_subset);
for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex)
{
const MeshComponent::MeshSubset& subset = mesh.subsets[subsetIndex];
const MaterialComponent* material = materials.GetComponent(subset.materialID);
if (material != nullptr)
{
object.filterMask |= material->GetFilterMask();
if (material->HasPlanarReflection())
{
object.SetRequestPlanarReflection(true);
}
sort_bits.bits.shadertype |= 1 << material->shaderType;
sort_bits.bits.blendmode |= 1 << material->GetBlendMode();
sort_bits.bits.doublesided |= material->IsDoubleSided();
sort_bits.bits.alphatest |= material->IsAlphaTestEnabled();
int customshader = material->GetCustomShaderID();
if (customshader >= 0)
{
sort_bits.bits.customshader |= 1 << customshader;
}
}
}
object.sort_bits = sort_bits.value;
// Correction matrix for mesh normals with non-uniform object scaling:
XMMATRIX worldMatrixInverseTranspose = XMMatrixTranspose(XMMatrixInverse(nullptr, W));
XMFLOAT4X4 transformIT;
XMStoreFloat4x4(&transformIT, worldMatrixInverseTranspose);
// Create GPU instance data:
GraphicsDevice* device = wi::graphics::GetDevice();
ShaderMeshInstance inst;
inst.init();
XMFLOAT4X4 worldMatrixPrev = matrix_objects[args.jobIndex];
matrix_objects_prev[args.jobIndex] = worldMatrixPrev;
XMStoreFloat4x4(matrix_objects.data() + args.jobIndex, W);
XMFLOAT4X4 worldMatrix = matrix_objects[args.jobIndex];
if (IsFormatUnorm(mesh.position_format) && !mesh.so_pos.IsValid())
{
// The UNORM correction is only done for the GPU data!
XMMATRIX R = mesh.aabb.getUnormRemapMatrix();
XMStoreFloat4x4(&worldMatrix, R * W);
XMStoreFloat4x4(&worldMatrixPrev, R * XMLoadFloat4x4(&worldMatrixPrev));
}
inst.transform.Create(worldMatrix);
inst.transformPrev.Create(worldMatrixPrev);
inst.transformInverseTranspose.Create(transformIT);
if (object.lightmap.IsValid())
{
inst.lightmap = device->GetDescriptorIndex(&object.lightmap, SubresourceType::SRV);
}
inst.uid = entity;
inst.layerMask = layerMask;
inst.color = wi::math::CompressColor(object.color);
inst.emissive = wi::math::Pack_R11G11B10_FLOAT(XMFLOAT3(object.emissiveColor.x * object.emissiveColor.w, object.emissiveColor.y * object.emissiveColor.w, object.emissiveColor.z * object.emissiveColor.w));
inst.baseGeometryOffset = mesh.geometryOffset;
inst.baseGeometryCount = (uint)mesh.subsets.size();
inst.geometryOffset = inst.baseGeometryOffset + first_subset;
inst.geometryCount = last_subset - first_subset;
inst.meshletOffset = meshletAllocator.fetch_add(mesh.meshletCount);
inst.fadeDistance = object.fadeDistance;
inst.center = object.center;
inst.radius = object.radius;
inst.vb_ao = object.vb_ao_srv;
inst.SetUserStencilRef(object.userStencilRef);
std::memcpy(instanceArrayMapped + args.jobIndex, &inst, sizeof(inst)); // memcpy whole structure into mapped pointer to avoid read from uncached memory
if (TLAS_instancesMapped != nullptr)
{
// TLAS instance data:
RaytracingAccelerationStructureDesc::TopLevel::Instance instance;
for (int i = 0; i < arraysize(instance.transform); ++i)
{
for (int j = 0; j < arraysize(instance.transform[i]); ++j)
{
instance.transform[i][j] = worldMatrix.m[j][i];
}
}
instance.instance_id = args.jobIndex;
instance.instance_mask = layerMask == 0 ? 0 : 0xFF;
if (!object.IsRenderable() || !mesh.IsRenderable())
{
instance.instance_mask = 0;
}
if (!object.IsCastingShadow())
{
instance.instance_mask &= ~wi::renderer::raytracing_inclusion_mask_shadow;
}
if (object.IsNotVisibleInReflections())
{
instance.instance_mask &= ~wi::renderer::raytracing_inclusion_mask_reflection;
}
instance.bottom_level = &mesh.BLASes[object.lod];
instance.instance_contribution_to_hit_group_index = 0;
instance.flags = 0;
if (mesh.IsDoubleSided() || mesh._flags & MeshComponent::TLAS_FORCE_DOUBLE_SIDED)
{
instance.flags |= RaytracingAccelerationStructureDesc::TopLevel::Instance::FLAG_TRIANGLE_CULL_DISABLE;
}
if (XMVectorGetX(XMMatrixDeterminant(W)) > 0)
{
// There is a mismatch between object space winding and BLAS winding:
// https://docs.microsoft.com/en-us/windows/win32/api/d3d12/ne-d3d12-d3d12_raytracing_instance_flags
instance.flags |= RaytracingAccelerationStructureDesc::TopLevel::Instance::FLAG_TRIANGLE_FRONT_COUNTERCLOCKWISE;
}
void* dest = (void*)((size_t)TLAS_instancesMapped + (size_t)args.jobIndex * device->GetTopLevelAccelerationStructureInstanceSize());
device->WriteTopLevelAccelerationStructureInstance(&instance, dest);
}
// lightmap things:
if (object.IsLightmapRenderRequested() && dt > 0)
{
if (!object.lightmap.IsValid())
{
object.lightmapWidth = wi::math::GetNextPowerOfTwo(object.lightmapWidth + 1) / 2;
object.lightmapHeight = wi::math::GetNextPowerOfTwo(object.lightmapHeight + 1) / 2;
TextureDesc desc;
desc.width = object.lightmapWidth;
desc.height = object.lightmapHeight;
desc.bind_flags = BindFlag::RENDER_TARGET | BindFlag::SHADER_RESOURCE;
// Note: we need the full precision format to achieve correct accumulative blending!
// But the final lightmap will be compressed into an optimal format when the rendering is finished
desc.format = Format::R32G32B32A32_FLOAT;
device->CreateTexture(&desc, nullptr, &object.lightmap);
device->SetName(&object.lightmap, "lightmap_renderable");
object.lightmapIterationCount = 0; // reset accumulation
}
}
if (!object.lightmapTextureData.empty() && !object.lightmap.IsValid())
{
// Create a GPU-side per object lightmap if there is none yet, but the data exists already:
const size_t lightmap_size = object.lightmapTextureData.size();
if (lightmap_size == object.lightmapWidth * object.lightmapHeight * sizeof(XMFLOAT4))
{
object.lightmap.desc.format = Format::R32G32B32A32_FLOAT;
}
else if (lightmap_size == object.lightmapWidth * object.lightmapHeight * sizeof(PackedVector::XMFLOAT3PK))
{
object.lightmap.desc.format = Format::R11G11B10_FLOAT;
}
else if (lightmap_size == (object.lightmapWidth / GetFormatBlockSize(Format::BC6H_UF16)) * (object.lightmapHeight / GetFormatBlockSize(Format::BC6H_UF16)) * GetFormatStride(Format::BC6H_UF16))
{
object.lightmap.desc.format = Format::BC6H_UF16;
}
else
{
assert(0); // unknown data format
}
wi::texturehelper::CreateTexture(object.lightmap, object.lightmapTextureData.data(), object.lightmapWidth, object.lightmapHeight, object.lightmap.desc.format);
device->SetName(&object.lightmap, "lightmap");
}
aabb.layerMask = layerMask;
// parallel bounds computation using shared memory:
AABB* shared_bounds = (AABB*)args.sharedmemory;
if (args.isFirstJobInGroup)
{
*shared_bounds = aabb_objects[args.jobIndex];
}
else
{
*shared_bounds = AABB::Merge(*shared_bounds, aabb_objects[args.jobIndex]);
}
if (args.isLastJobInGroup)
{
parallel_bounds[args.groupID] = *shared_bounds;
}
}
}, sizeof(AABB));
}
void Scene::RunCameraUpdateSystem(wi::jobsystem::context& ctx)
{
wi::jobsystem::Dispatch(ctx, (uint32_t)cameras.GetCount(), small_subtask_groupsize, [&](wi::jobsystem::JobArgs args) {
CameraComponent& camera = cameras[args.jobIndex];
Entity entity = cameras.GetEntity(args.jobIndex);
const TransformComponent* transform = transforms.GetComponent(entity);
if (transform != nullptr)
{
camera.TransformCamera(*transform);
}
camera.UpdateCamera();
});
}
void Scene::RunDecalUpdateSystem(wi::jobsystem::context& ctx)
{
aabb_decals.resize(decals.GetCount());
for (size_t i = 0; i < decals.GetCount(); ++i)
{
DecalComponent& decal = decals[i];
Entity entity = decals.GetEntity(i);
if (!transforms.Contains(entity))
continue;
const TransformComponent& transform = *transforms.GetComponent(entity);
decal.world = transform.world;
XMMATRIX W = XMLoadFloat4x4(&decal.world);
XMVECTOR front = XMVectorSet(0, 0, -1, 0);
front = XMVector3TransformNormal(front, W);
front = XMVector3Normalize(front);
XMStoreFloat3(&decal.front, front);
XMVECTOR S, R, T;
XMMatrixDecompose(&S, &R, &T, W);
XMStoreFloat3(&decal.position, T);
XMFLOAT3 scale;
XMStoreFloat3(&scale, S);
decal.range = std::max(scale.x, std::max(scale.y, scale.z)) * 2;
AABB& aabb = aabb_decals[i];
aabb.createFromHalfWidth(XMFLOAT3(0, 0, 0), XMFLOAT3(1, 1, 1));
aabb = aabb.transform(transform.world);
const LayerComponent* layer = layers.GetComponent(entity);
if (layer == nullptr)
{
aabb.layerMask = ~0;
}
else
{
aabb.layerMask = layer->GetLayerMask();
}
const MaterialComponent& material = *materials.GetComponent(entity);
decal.color = material.baseColor;
decal.emissive = material.GetEmissiveStrength();
decal.texture = material.textures[MaterialComponent::BASECOLORMAP].resource;
decal.normal = material.textures[MaterialComponent::NORMALMAP].resource;
decal.surfacemap = material.textures[MaterialComponent::SURFACEMAP].resource;
decal.displacementmap = material.textures[MaterialComponent::DISPLACEMENTMAP].resource;
decal.normal_strength = material.normalMapStrength;
decal.displacement_strength = material.parallaxOcclusionMapping;
decal.texMulAdd = material.texMulAdd;
}
}
void Scene::RunProbeUpdateSystem(wi::jobsystem::context& ctx)
{
aabb_probes.resize(probes.GetCount());
if (dt == 0)
return;
for (size_t probeIndex = 0; probeIndex < probes.GetCount(); ++probeIndex)
{
EnvironmentProbeComponent& probe = probes[probeIndex];
Entity entity = probes.GetEntity(probeIndex);
if (!transforms.Contains(entity))
continue;
const TransformComponent& transform = *transforms.GetComponent(entity);
probe.position = transform.GetPosition();
XMMATRIX W = XMLoadFloat4x4(&transform.world);
XMStoreFloat4x4(&probe.inverseMatrix, XMMatrixInverse(nullptr, W));
XMVECTOR S, R, T;
XMMatrixDecompose(&S, &R, &T, W);
XMFLOAT3 scale;
XMStoreFloat3(&scale, S);
probe.range = std::max(scale.x, std::max(scale.y, scale.z)) * 2;
AABB& aabb = aabb_probes[probeIndex];
aabb.createFromHalfWidth(XMFLOAT3(0, 0, 0), XMFLOAT3(1, 1, 1));
aabb = aabb.transform(transform.world);
const LayerComponent* layer = layers.GetComponent(entity);
if (layer == nullptr)
{
aabb.layerMask = ~0;
}
else
{
aabb.layerMask = layer->GetLayerMask();
}
if (probe.IsDirty() || probe.IsRealTime())
{
probe.SetDirty(false);
probe.render_dirty = true;
}
probe.CreateRenderData();
}
if (probes.GetCount() == 0)
{
global_dynamic_probe.SetRealTime(true);
global_dynamic_probe.resolution = 64;
global_dynamic_probe.CreateRenderData();
}
}
void Scene::RunForceUpdateSystem(wi::jobsystem::context& ctx)
{
wi::jobsystem::Dispatch(ctx, (uint32_t)forces.GetCount(), small_subtask_groupsize, [&](wi::jobsystem::JobArgs args) {
ForceFieldComponent& force = forces[args.jobIndex];
Entity entity = forces.GetEntity(args.jobIndex);
if (!transforms.Contains(entity))
return;
const TransformComponent& transform = *transforms.GetComponent(entity);
XMMATRIX W = XMLoadFloat4x4(&transform.world);
XMVECTOR S, R, T;
XMMatrixDecompose(&S, &R, &T, W);
XMStoreFloat3(&force.position, T);
XMStoreFloat3(&force.direction, XMVector3Normalize(XMVector3TransformNormal(XMVectorSet(0, -1, 0, 0), W)));
});
}
void Scene::RunLightUpdateSystem(wi::jobsystem::context& ctx)
{
aabb_lights.resize(lights.GetCount());
wi::jobsystem::Dispatch(ctx, (uint32_t)lights.GetCount(), small_subtask_groupsize, [&](wi::jobsystem::JobArgs args) {
LightComponent& light = lights[args.jobIndex];
Entity entity = lights.GetEntity(args.jobIndex);
if (!transforms.Contains(entity))
return;
const TransformComponent& transform = *transforms.GetComponent(entity);
AABB& aabb = aabb_lights[args.jobIndex];
light.occlusionquery = -1;
const LayerComponent* layer = layers.GetComponent(entity);
if (layer == nullptr)
{
aabb.layerMask = ~0;
}
else
{
aabb.layerMask = layer->GetLayerMask();
}
XMMATRIX W = XMLoadFloat4x4(&transform.world);
XMVECTOR S, R, T;
XMMatrixDecompose(&S, &R, &T, W);
XMStoreFloat3(&light.position, T);
XMStoreFloat4(&light.rotation, R);
XMStoreFloat3(&light.scale, S);
XMStoreFloat3(&light.direction, XMVector3Normalize(XMVector3TransformNormal(XMVectorSet(0, 1, 0, 0), W)));
switch (light.type)
{
default:
case LightComponent::DIRECTIONAL:
XMStoreFloat3(&light.direction, XMVector3Normalize(XMVector3TransformNormal(XMVectorSet(0, 1, 0, 0), W)));
aabb.createFromHalfWidth(XMFLOAT3(0, 0, 0), XMFLOAT3(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max()));
locker.lock();
if (args.jobIndex < weather.most_important_light_index)
{
weather.most_important_light_index = args.jobIndex;
weather.sunColor = light.color;
weather.sunColor.x *= light.intensity;
weather.sunColor.y *= light.intensity;
weather.sunColor.z *= light.intensity;
weather.sunDirection = light.direction;
weather.stars_rotation_quaternion = light.rotation;
}
locker.unlock();
break;
case LightComponent::SPOT:
XMStoreFloat3(&light.direction, XMVector3Normalize(XMVector3TransformNormal(XMVectorSet(0, 1, 0, 0), W)));
aabb.createFromHalfWidth(light.position, XMFLOAT3(light.GetRange(), light.GetRange(), light.GetRange()));
break;
case LightComponent::POINT:
XMStoreFloat3(&light.direction, XMVector3Normalize(XMVector3TransformNormal(XMVectorSet(1, 0, 0, 0), W)));
aabb.createFromHalfWidth(light.position, XMFLOAT3(light.GetRange(), light.GetRange(), light.GetRange()));
break;
}
});
}
void Scene::RunParticleUpdateSystem(wi::jobsystem::context& ctx)
{
wi::jobsystem::Dispatch(ctx, (uint32_t)hairs.GetCount(), small_subtask_groupsize, [&](wi::jobsystem::JobArgs args) {
HairParticleSystem& hair = hairs[args.jobIndex];
Entity entity = hairs.GetEntity(args.jobIndex);
if (!transforms.Contains(entity))
return;
if (hair.IsDirty())
{
hair.SetDirty(false);
}
const LayerComponent* layer = layers.GetComponent(entity);
if (layer != nullptr)
{
hair.layerMask = layer->GetLayerMask();
}
if (hair.meshID != INVALID_ENTITY)
{
const MeshComponent* mesh = meshes.GetComponent(hair.meshID);
if (mesh != nullptr)
{
const TransformComponent& transform = *transforms.GetComponent(entity);
hair.UpdateCPU(transform, *mesh, dt);
}
}
GraphicsDevice* device = wi::graphics::GetDevice();
uint32_t indexCount = hair.GetParticleCount() * 6;
uint32_t triangleCount = indexCount / 3u;
uint32_t meshletCount = triangle_count_to_meshlet_count(triangleCount);
uint32_t meshletOffset = meshletAllocator.fetch_add(meshletCount);
ShaderGeometry geometry;
geometry.init();
geometry.indexOffset = 0;
geometry.indexCount = indexCount;
geometry.materialIndex = (uint)materials.GetIndex(entity);
geometry.ib = device->GetDescriptorIndex(&hair.primitiveBuffer, SubresourceType::SRV);
geometry.vb_pos_wind = hair.vb_pos[0].descriptor_srv;
geometry.vb_nor = hair.vb_nor.descriptor_srv;
geometry.vb_pre = hair.vb_pos[1].descriptor_srv;
geometry.vb_uvs = hair.vb_uvs.descriptor_srv;
geometry.flags = SHADERMESH_FLAG_DOUBLE_SIDED | SHADERMESH_FLAG_HAIRPARTICLE;
geometry.meshletOffset = 0;
geometry.meshletCount = meshletCount;
geometry.aabb_min = hair.aabb._min;
geometry.aabb_max = hair.aabb._max;
size_t geometryAllocation = geometryAllocator.fetch_add(1);
std::memcpy(geometryArrayMapped + geometryAllocation, &geometry, sizeof(geometry));
ShaderMeshInstance inst;
inst.init();
inst.uid = entity;
inst.layerMask = hair.layerMask;
inst.emissive = wi::math::Pack_R11G11B10_FLOAT(XMFLOAT3(1, 1, 1));
inst.color = wi::math::CompressColor(XMFLOAT4(1, 1, 1, 1));
inst.center = hair.aabb.getCenter();
inst.radius = hair.aabb.getRadius();
inst.geometryOffset = (uint)geometryAllocation;
inst.geometryCount = 1;
inst.baseGeometryOffset = inst.geometryOffset;
inst.baseGeometryCount = inst.geometryCount;
inst.meshletOffset = meshletOffset;
XMFLOAT4X4 remapMatrix;
XMStoreFloat4x4(&remapMatrix, hair.aabb.getUnormRemapMatrix());
inst.transform.Create(remapMatrix);
inst.transformPrev = inst.transform;
const size_t instanceIndex = objects.GetCount() + args.jobIndex;
std::memcpy(instanceArrayMapped + instanceIndex, &inst, sizeof(inst));
if (TLAS_instancesMapped != nullptr)
{
if (!hair.BLAS.IsValid())
{
hair.CreateRaytracingRenderData();
}
if (hair.BLAS.IsValid())
{
// TLAS instance data:
RaytracingAccelerationStructureDesc::TopLevel::Instance instance;
for (int i = 0; i < arraysize(instance.transform); ++i)
{
for (int j = 0; j < arraysize(instance.transform[i]); ++j)
{
instance.transform[i][j] = remapMatrix.m[j][i];
}
}
instance.instance_id = (uint32_t)instanceIndex;
instance.instance_mask = hair.layerMask == 0 ? 0 : 0xFF;
instance.bottom_level = &hair.BLAS;
instance.instance_contribution_to_hit_group_index = 0;
instance.flags = RaytracingAccelerationStructureDesc::TopLevel::Instance::FLAG_TRIANGLE_CULL_DISABLE;
void* dest = (void*)((size_t)TLAS_instancesMapped + instanceIndex * device->GetTopLevelAccelerationStructureInstanceSize());
device->WriteTopLevelAccelerationStructureInstance(&instance, dest);
}
}
});
wi::jobsystem::Dispatch(ctx, (uint32_t)emitters.GetCount(), small_subtask_groupsize, [&](wi::jobsystem::JobArgs args) {
EmittedParticleSystem& emitter = emitters[args.jobIndex];
Entity entity = emitters.GetEntity(args.jobIndex);
if (!transforms.Contains(entity))
return;
MaterialComponent* material = materials.GetComponent(entity);
if (material != nullptr)
{
if (!material->IsUsingVertexColors())
{
material->SetUseVertexColors(true);
}
if (emitter.shaderType == EmittedParticleSystem::PARTICLESHADERTYPE::SOFT_LIGHTING)
{
material->shaderType = MaterialComponent::SHADERTYPE_PBR;
}
else
{
material->shaderType = MaterialComponent::SHADERTYPE_UNLIT;
}
}
const LayerComponent* layer = layers.GetComponent(entity);
if (layer != nullptr)
{
emitter.layerMask = layer->GetLayerMask();
}
const TransformComponent& transform = *transforms.GetComponent(entity);
emitter.UpdateCPU(transform, dt);
GraphicsDevice* device = wi::graphics::GetDevice();
ShaderGeometry geometry;
geometry.init();
geometry.indexOffset = 0;
geometry.indexCount = emitter.GetMaxParticleCount() * 6;
geometry.materialIndex = (uint)materials.GetIndex(entity);
geometry.ib = device->GetDescriptorIndex(&emitter.primitiveBuffer, SubresourceType::SRV);
geometry.vb_pos_wind = emitter.vb_pos.descriptor_srv;
geometry.vb_nor = emitter.vb_nor.descriptor_srv;
geometry.vb_uvs = emitter.vb_uvs.descriptor_srv;
geometry.vb_col = emitter.vb_col.descriptor_srv;
geometry.flags = SHADERMESH_FLAG_DOUBLE_SIDED | SHADERMESH_FLAG_EMITTEDPARTICLE;
size_t geometryAllocation = geometryAllocator.fetch_add(1);
std::memcpy(geometryArrayMapped + geometryAllocation, &geometry, sizeof(geometry));
ShaderMeshInstance inst;
inst.init();
inst.uid = entity;
inst.layerMask = emitter.layerMask;
inst.emissive = wi::math::Pack_R11G11B10_FLOAT(XMFLOAT3(1, 1, 1));
inst.color = wi::math::CompressColor(XMFLOAT4(1, 1, 1, 1));
inst.geometryOffset = (uint)geometryAllocation;
inst.geometryCount = 1;
inst.baseGeometryOffset = inst.geometryOffset;
inst.baseGeometryCount = inst.geometryCount;
const size_t instanceIndex = objects.GetCount() + hairs.GetCount() + args.jobIndex;
std::memcpy(instanceArrayMapped + instanceIndex, &inst, sizeof(inst));
if (TLAS_instancesMapped != nullptr)
{
if (!emitter.BLAS.IsValid())
{
emitter.CreateRaytracingRenderData();
}
// TLAS instance data:
RaytracingAccelerationStructureDesc::TopLevel::Instance instance;
for (int i = 0; i < arraysize(instance.transform); ++i)
{
for (int j = 0; j < arraysize(instance.transform[i]); ++j)
{
instance.transform[i][j] = wi::math::IDENTITY_MATRIX.m[j][i];
}
}
instance.instance_id = (uint32_t)instanceIndex;
instance.instance_mask = emitter.layerMask == 0 ? 0 : 0xFF;
instance.bottom_level = &emitter.BLAS;
instance.instance_contribution_to_hit_group_index = 0;
instance.flags = RaytracingAccelerationStructureDesc::TopLevel::Instance::FLAG_TRIANGLE_CULL_DISABLE;
void* dest = (void*)((size_t)TLAS_instancesMapped + instanceIndex * device->GetTopLevelAccelerationStructureInstanceSize());
device->WriteTopLevelAccelerationStructureInstance(&instance, dest);
}
});
}
void Scene::RunWeatherUpdateSystem(wi::jobsystem::context& ctx)
{
if (weathers.GetCount() > 0)
{
weather = weathers[0];
weather.most_important_light_index = ~0;
if (weather.IsOceanEnabled() && !ocean.IsValid())
{
OceanRegenerate();
}
// Ocean occlusion status:
if (!wi::renderer::GetFreezeCullingCameraEnabled() && weather.IsOceanEnabled())
{
ocean.occlusionHistory <<= 1u; // advance history by 1 frame
int query_id = ocean.occlusionQueries[queryheap_idx];
if (queryResultBuffer[queryheap_idx].mapped_data != nullptr && query_id >= 0)
{
uint64_t visible = ((uint64_t*)queryResultBuffer[queryheap_idx].mapped_data)[query_id];
if (visible)
{
ocean.occlusionHistory |= 1; // visible
}
}
else
{
ocean.occlusionHistory |= 1; // visible
}
}
ocean.occlusionQueries[queryheap_idx] = -1; // invalidate query
}
if (weather.rain_amount > 0)
{
GraphicsDevice* device = wi::graphics::GetDevice();
rainEmitter._flags |= wi::EmittedParticleSystem::FLAG_USE_RAIN_BLOCKER;
rainEmitter.shaderType = wi::EmittedParticleSystem::PARTICLESHADERTYPE::SOFT_LIGHTING;
rainEmitter.SetCollidersDisabled(true);
rainEmitter.SetVolumeEnabled(true);
constexpr uint32_t target_max_particle_count = 1000000;
if (rainEmitter.GetMaxParticleCount() != target_max_particle_count)
{
rainEmitter.SetMaxParticleCount(target_max_particle_count);
}
rainEmitter.count = wi::math::Lerp(0, (float)target_max_particle_count, weather.rain_amount);
rainEmitter.life = 1;
rainEmitter.size = weather.rain_scale;
rainEmitter.random_factor = weather.windRandomness;
rainEmitter.random_life = 1;
rainEmitter.motionBlurAmount = weather.rain_length;
rainEmitter.velocity = XMFLOAT3(
weather.windDirection.x * weather.windSpeed,
-weather.rain_speed,
weather.windDirection.z * weather.windSpeed
);
rainMaterial.SetUseVertexColors(true);
rainMaterial.shaderType = MaterialComponent::SHADERTYPE_PBR;
rainMaterial.subsurfaceScattering = XMFLOAT4(1, 1, 1, 2);
rainMaterial.userBlendMode = BLENDMODE_ALPHA;
rainMaterial.baseColor = weather.rain_color;
if (!rainMaterial.textures[MaterialComponent::BASECOLORMAP].resource.IsValid())
{
Texture gradientTex = wi::texturehelper::CreateGradientTexture(
wi::texturehelper::GradientType::Circular,
32, 32,
XMFLOAT2(0.5f, 0.5f), XMFLOAT2(0.5f, 0),
wi::texturehelper::GradientFlags::Smoothstep | wi::texturehelper::GradientFlags::Inverse
);
Texture gradientTexBC;
TextureDesc desc = gradientTex.GetDesc();
desc.format = Format::BC4_UNORM;
desc.swizzle = { wi::graphics::ComponentSwizzle::ONE,wi::graphics::ComponentSwizzle::ONE,wi::graphics::ComponentSwizzle::ONE,wi::graphics::ComponentSwizzle::R };
bool success = device->CreateTexture(&desc, nullptr, &gradientTexBC);
assert(success);
wi::renderer::AddDeferredBlockCompression(gradientTex, gradientTexBC);
rainMaterial.textures[MaterialComponent::BASECOLORMAP].resource.SetTexture(gradientTexBC);
}
rainMaterial.shadingRate = ShadingRate::RATE_4X4;
TransformComponent transform;
transform.scale_local = XMFLOAT3(30, 30, 30);
transform.translation_local.x = camera.Eye.x + camera.At.x * 10;
transform.translation_local.y = camera.Eye.y + camera.At.y * 10 + transform.scale_local.y * 0.5f;
transform.translation_local.z = camera.Eye.z + camera.At.z * 10;
transform.UpdateTransform();
rainEmitter.UpdateCPU(transform, dt);
rain_blocker_dummy_light.cascade_distances[0] = transform.scale_local.x;
ShaderMaterial material;
material.init();
rainMaterial.WriteShaderMaterial(&material);
std::memcpy(materialArrayMapped + rainMaterialOffset, &material, sizeof(material));
ShaderGeometry geometry;
geometry.init();
geometry.indexOffset = 0;
geometry.indexCount = rainEmitter.GetMaxParticleCount() * 6;
geometry.materialIndex = rainMaterialOffset;
geometry.ib = device->GetDescriptorIndex(&rainEmitter.primitiveBuffer, SubresourceType::SRV);
geometry.vb_pos_wind = rainEmitter.vb_pos.descriptor_srv;
geometry.vb_nor = rainEmitter.vb_nor.descriptor_srv;
geometry.vb_uvs = rainEmitter.vb_uvs.descriptor_srv;
geometry.vb_col = rainEmitter.vb_col.descriptor_srv;
geometry.flags = SHADERMESH_FLAG_DOUBLE_SIDED | SHADERMESH_FLAG_EMITTEDPARTICLE;
std::memcpy(geometryArrayMapped + rainGeometryOffset, &geometry, sizeof(geometry));
ShaderMeshInstance inst;
inst.init();
inst.uid = 0;
inst.layerMask = ~0u;
inst.emissive = wi::math::Pack_R11G11B10_FLOAT(XMFLOAT3(1, 1, 1));
inst.color = wi::math::CompressColor(XMFLOAT4(1, 1, 1, 1));
inst.geometryOffset = (uint)rainGeometryOffset;
inst.geometryCount = 1;
inst.baseGeometryOffset = inst.geometryOffset;
inst.baseGeometryCount = inst.geometryCount;
const size_t instanceIndex = rainInstanceOffset;
std::memcpy(instanceArrayMapped + instanceIndex, &inst, sizeof(inst));
if (TLAS_instancesMapped != nullptr)
{
if (!rainEmitter.BLAS.IsValid())
{
rainEmitter.CreateRaytracingRenderData();
}
// TLAS instance data:
RaytracingAccelerationStructureDesc::TopLevel::Instance instance;
for (int i = 0; i < arraysize(instance.transform); ++i)
{
for (int j = 0; j < arraysize(instance.transform[i]); ++j)
{
instance.transform[i][j] = wi::math::IDENTITY_MATRIX.m[j][i];
}
}
instance.instance_id = (uint32_t)instanceIndex;
instance.instance_mask = rainEmitter.layerMask == 0 ? 0 : 0xFF;
instance.bottom_level = &rainEmitter.BLAS;
instance.instance_contribution_to_hit_group_index = 0;
instance.flags = RaytracingAccelerationStructureDesc::TopLevel::Instance::FLAG_TRIANGLE_CULL_DISABLE;
void* dest = (void*)((size_t)TLAS_instancesMapped + instanceIndex * device->GetTopLevelAccelerationStructureInstanceSize());
device->WriteTopLevelAccelerationStructureInstance(&instance, dest);
}
}
else
{
rainMaterial = {};
rainEmitter = {};
}
}
void Scene::RunSoundUpdateSystem(wi::jobsystem::context& ctx)
{
wi::audio::SoundInstance3D instance3D;
instance3D.listenerPos = camera.Eye;
instance3D.listenerUp = camera.Up;
instance3D.listenerFront = camera.At;
for (size_t i = 0; i < sounds.GetCount(); ++i)
{
SoundComponent& sound = sounds[i];
if (!sound.soundinstance.IsValid() && sound.soundResource.IsValid())
{
sound.soundinstance.SetLooped(sound.IsLooped());
wi::audio::CreateSoundInstance(&sound.soundResource.GetSound(), &sound.soundinstance);
}
if (!sound.IsDisable3D())
{
Entity entity = sounds.GetEntity(i);
const TransformComponent* transform = transforms.GetComponent(entity);
if (transform != nullptr)
{
instance3D.emitterPos = transform->GetPosition();
instance3D.emitterFront = transform->GetForward();
instance3D.emitterUp = transform->GetUp();
wi::audio::Update3D(&sound.soundinstance, instance3D);
}
}
if (sound.IsPlaying())
{
wi::audio::Play(&sound.soundinstance);
}
else
{
wi::audio::Stop(&sound.soundinstance);
}
wi::audio::SetVolume(sound.volume, &sound.soundinstance);
}
}
void Scene::RunVideoUpdateSystem(wi::jobsystem::context& ctx)
{
for (size_t i = 0; i < videos.GetCount(); ++i)
{
VideoComponent& video = videos[i];
if (video.IsPlaying())
{
video.videoinstance.flags |= wi::video::VideoInstance::Flags::Playing;
}
else
{
video.videoinstance.flags &= ~wi::video::VideoInstance::Flags::Playing;
}
if (video.IsLooped())
{
video.videoinstance.flags |= wi::video::VideoInstance::Flags::Looped;
}
else
{
video.videoinstance.flags &= ~wi::video::VideoInstance::Flags::Looped;
}
video.videoinstance.flags |= wi::video::VideoInstance::Flags::Mipmapped;
}
}
void Scene::RunScriptUpdateSystem(wi::jobsystem::context& ctx)
{
if (dt == 0)
return; // not allowed to be run when dt == 0 as it could be on separate thread!
auto range = wi::profiler::BeginRangeCPU("Script Components");
for (size_t i = 0; i < scripts.GetCount(); ++i)
{
ScriptComponent& script = scripts[i];
Entity entity = scripts.GetEntity(i);
if (script.IsPlaying())
{
if (script.script.empty() && script.resource.IsValid())
{
std::string str = script.resource.GetScript();
wi::lua::AttachScriptParameters(str, script.filename, wi::lua::GeneratePID(), "local function GetEntity() return " + std::to_string(entity) + "; end;", "");
wi::lua::CompileText(str, script.script);
}
if (!script.script.empty())
{
wi::lua::RunBinaryData(script.script.data(), script.script.size(), script.filename.c_str());
}
if (script.IsPlayingOnlyOnce())
{
script.Stop();
}
}
}
wi::profiler::EndRange(range);
}
void Scene::RunSpriteUpdateSystem(wi::jobsystem::context& ctx)
{
wi::jobsystem::Dispatch(ctx, (uint32_t)sprites.GetCount(), small_subtask_groupsize, [&](wi::jobsystem::JobArgs args) {
Sprite& sprite = sprites[args.jobIndex];
if (sprite.params.isExtractNormalMapEnabled())
{
sprite.params.image_subresource = -1;
}
else if (sprite.textureResource.IsValid())
{
sprite.params.image_subresource = sprite.textureResource.GetTextureSRGBSubresource();
}
if (sprite.maskResource.IsValid())
{
sprite.params.mask_subresource = sprite.maskResource.GetTextureSRGBSubresource();
}
sprite.Update(dt);
});
}
void Scene::RunFontUpdateSystem(wi::jobsystem::context& ctx)
{
wi::jobsystem::Dispatch(ctx, (uint32_t)fonts.GetCount(), small_subtask_groupsize, [&](wi::jobsystem::JobArgs args) {
SpriteFont& font = fonts[args.jobIndex];
Entity entity = fonts.GetEntity(args.jobIndex);
const SoundComponent* sound = sounds.GetComponent(entity);
if (sound != nullptr && sound->soundResource.IsValid())
{
font.anim.typewriter.sound = sound->soundResource.GetSound();
font.anim.typewriter.soundinstance = sound->soundinstance;
}
else
{
font.anim.typewriter.sound = {};
font.anim.typewriter.soundinstance = {};
}
font.Update(dt);
});
}
Scene::RayIntersectionResult Scene::Intersects(const Ray& ray, uint32_t filterMask, uint32_t layerMask, uint32_t lod) const
{
RayIntersectionResult result;
const XMVECTOR rayOrigin = XMLoadFloat3(&ray.origin);
const XMVECTOR rayDirection = XMVector3Normalize(XMLoadFloat3(&ray.direction));
if ((filterMask & FILTER_COLLIDER) && collider_bvh.IsValid())
{
collider_bvh.Intersects(ray, 0, [&](uint32_t collider_index) {
const ColliderComponent& collider = colliders_cpu[collider_index];
if ((collider.layerMask & layerMask) == 0)
return;
float dist = 0;
XMFLOAT3 direction = {};
bool intersects = false;
switch (collider.shape)
{
default:
case ColliderComponent::Shape::Sphere:
intersects = ray.intersects(collider.sphere, dist, direction);
break;
case ColliderComponent::Shape::Capsule:
intersects = ray.intersects(collider.capsule, dist, direction);
break;
case ColliderComponent::Shape::Plane:
intersects = ray.intersects(collider.plane, dist, direction);
break;
}
if (intersects)
{
if (dist < result.distance)
{
result.distance = dist;
result.bary = {};
result.entity = colliders.GetEntity(collider_index);
result.normal = direction;
result.uv = {};
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)
{
for (size_t objectIndex = 0; objectIndex < aabb_objects.size(); ++objectIndex)
{
const AABB& aabb = aabb_objects[objectIndex];
if (!ray.intersects(aabb) || (layerMask & aabb.layerMask) == 0)
continue;
const ObjectComponent& object = objects[objectIndex];
if (object.meshID == INVALID_ENTITY)
continue;
if ((filterMask & object.GetFilterMask()) == 0)
continue;
const MeshComponent* mesh = meshes.GetComponent(object.meshID);
if (mesh == nullptr)
continue;
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)
{
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];
XMVECTOR p0;
XMVECTOR p1;
XMVECTOR p2;
const bool softbody_active = softbody != nullptr && softbody->HasVertices();
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 = XMLoadFloat3(&mesh->vertex_positions[i0]);
p1 = XMLoadFloat3(&mesh->vertex_positions[i1]);
p2 = XMLoadFloat3(&mesh->vertex_positions[i2]);
}
else
{
p0 = SkinVertex(*mesh, *armature, i0);
p1 = SkinVertex(*mesh, *armature, i1);
p2 = SkinVertex(*mesh, *armature, i2);
}
}
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)
{
XMVECTOR nor;
if (softbody != nullptr || mesh->vertex_normals.empty()) // Note: for soft body we compute it instead of loading the simulated normals
{
nor = XMVector3Cross(p2 - p1, p1 - p0);
}
else
{
nor = XMVectorBaryCentric(
XMLoadFloat3(&mesh->vertex_normals[i0]),
XMLoadFloat3(&mesh->vertex_normals[i1]),
XMLoadFloat3(&mesh->vertex_normals[i2]),
bary.x,
bary.y
);
}
nor = XMVector3Normalize(XMVector3TransformNormal(nor, objectMat));
const XMVECTOR vel = pos - XMVector3Transform(pos_local, objectMatPrev);
result.uv = {};
if (!mesh->vertex_uvset_0.empty())
{
XMVECTOR uv = XMVectorBaryCentric(
XMLoadFloat2(&mesh->vertex_uvset_0[i0]),
XMLoadFloat2(&mesh->vertex_uvset_0[i1]),
XMLoadFloat2(&mesh->vertex_uvset_0[i2]),
bary.x,
bary.y
);
result.uv.x = XMVectorGetX(uv);
result.uv.y = XMVectorGetY(uv);
}
if (!mesh->vertex_uvset_1.empty())
{
XMVECTOR uv = XMVectorBaryCentric(
XMLoadFloat2(&mesh->vertex_uvset_1[i0]),
XMLoadFloat2(&mesh->vertex_uvset_1[i1]),
XMLoadFloat2(&mesh->vertex_uvset_1[i2]),
bary.x,
bary.y
);
result.uv.z = XMVectorGetX(uv);
result.uv.w = XMVectorGetY(uv);
}
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 intersection 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);
}
}
}
}
}
result.orientation = ray.GetPlacementOrientation(result.position, result.normal);
return result;
}
bool Scene::IntersectsFirst(const wi::primitive::Ray& ray, uint32_t filterMask, uint32_t layerMask, uint32_t lod) const
{
bool result = false;
const XMVECTOR rayOrigin = XMLoadFloat3(&ray.origin);
const XMVECTOR rayDirection = XMVector3Normalize(XMLoadFloat3(&ray.direction));
if ((filterMask & FILTER_COLLIDER) && collider_bvh.IsValid())
{
collider_bvh.IntersectsFirst(ray, [&](uint32_t collider_index) {
const ColliderComponent& collider = colliders_cpu[collider_index];
if ((collider.layerMask & layerMask) == 0)
return false;
float dist = 0;
XMFLOAT3 direction = {};
bool intersects = false;
switch (collider.shape)
{
default:
case ColliderComponent::Shape::Sphere:
intersects = ray.intersects(collider.sphere, dist, direction);
break;
case ColliderComponent::Shape::Capsule:
intersects = ray.intersects(collider.capsule, dist, direction);
break;
case ColliderComponent::Shape::Plane:
intersects = ray.intersects(collider.plane, dist, direction);
break;
}
if (intersects)
{
result = true;
return true;
}
return false;
});
if (result)
return result;
}
if (filterMask & FILTER_OBJECT_ALL)
{
for (size_t objectIndex = 0; objectIndex < aabb_objects.size(); ++objectIndex)
{
const AABB& aabb = aabb_objects[objectIndex];
if (!ray.intersects(aabb) || (layerMask & aabb.layerMask) == 0)
continue;
const ObjectComponent& object = objects[objectIndex];
if (object.meshID == INVALID_ENTITY)
continue;
if ((filterMask & object.GetFilterMask()) == 0)
continue;
const MeshComponent* mesh = meshes.GetComponent(object.meshID);
if (mesh == nullptr)
continue;
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)
{
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];
XMVECTOR p0;
XMVECTOR p1;
XMVECTOR p2;
const bool softbody_active = softbody != nullptr && softbody->HasVertices();
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 = XMLoadFloat3(&mesh->vertex_positions[i0]);
p1 = XMLoadFloat3(&mesh->vertex_positions[i1]);
p2 = XMLoadFloat3(&mesh->vertex_positions[i2]);
}
else
{
p0 = SkinVertex(*mesh, *armature, i0);
p1 = SkinVertex(*mesh, *armature, i1);
p2 = SkinVertex(*mesh, *armature, i2);
}
}
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 >= ray.TMin && distance <= ray.TMax)
{
result = true;
return true;
}
}
return false;
};
if (mesh->bvh.IsValid())
{
Ray ray_local = Ray(rayOrigin_local, rayDirection_local);
mesh->bvh.IntersectsFirst(ray_local, [&](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 false;
const uint32_t indexOffset = subset.indexOffset;
return intersect_triangle(subsetIndex, indexOffset, triangleIndex);
});
}
else
{
// Brute-force intersection 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)
{
if (intersect_triangle(subsetIndex, indexOffset, triangleIndex))
{
result = true;
return true;
}
}
}
}
}
}
return result;
}
Scene::SphereIntersectionResult Scene::Intersects(const Sphere& sphere, uint32_t filterMask, uint32_t layerMask, uint32_t lod) const
{
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())
{
collider_bvh.Intersects(sphere, 0, [&](uint32_t collider_index) {
const ColliderComponent& collider = colliders_cpu[collider_index];
if ((collider.layerMask & layerMask) == 0)
return;
float dist = 0;
XMFLOAT3 direction = {};
XMFLOAT3 position = {};
bool intersects = false;
switch (collider.shape)
{
default:
case ColliderComponent::Shape::Sphere:
intersects = sphere.intersects(collider.sphere, dist, direction);
XMStoreFloat3(&position, XMLoadFloat3(&collider.sphere.center) + XMLoadFloat3(&direction) * dist);
break;
case ColliderComponent::Shape::Capsule:
intersects = sphere.intersects(collider.capsule, dist, direction);
break;
case ColliderComponent::Shape::Plane:
intersects = sphere.intersects(collider.plane, dist, direction);
break;
}
if (intersects)
{
if (dist > result.depth)
{
result.depth = dist;
result.entity = colliders.GetEntity(collider_index);
result.normal = direction;
result.position = position;
result.velocity = {};
}
}
});
}
if (filterMask & FILTER_OBJECT_ALL)
{
for (size_t objectIndex = 0; objectIndex < aabb_objects.size(); ++objectIndex)
{
const AABB& aabb = aabb_objects[objectIndex];
if (!sphere.intersects(aabb) || (layerMask & aabb.layerMask) == 0)
continue;
const ObjectComponent& object = objects[objectIndex];
if (object.meshID == INVALID_ENTITY)
continue;
if ((filterMask & object.GetFilterMask()) == 0)
continue;
const MeshComponent* mesh = meshes.GetComponent(object.meshID);
if (mesh == nullptr)
continue;
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)
{
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];
XMVECTOR p0;
XMVECTOR p1;
XMVECTOR p2;
const bool softbody_active = softbody != nullptr && softbody->HasVertices();
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 = XMLoadFloat3(&mesh->vertex_positions[i0]);
p1 = XMLoadFloat3(&mesh->vertex_positions[i1]);
p2 = XMLoadFloat3(&mesh->vertex_positions[i2]);
}
else
{
p0 = SkinVertex(*mesh, *armature, i0);
p1 = SkinVertex(*mesh, *armature, i1);
p2 = SkinVertex(*mesh, *armature, i2);
}
}
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 (sphere.intersects(aabb_triangle) == AABB::OUTSIDE)
return;
// Compute the plane of the triangle (has to be normalized).
XMVECTOR N = XMVector3Normalize(XMVector3Cross(p1 - p0, p2 - p0));
// 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 = 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(Center, Point1)), RadiusSq));
// 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(Center, Point2)), RadiusSq));
// 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(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 = 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);
result.subsetIndex = (int)subsetIndex;
}
}
};
if (mesh->bvh.IsValid())
{
XMFLOAT3 center_local;
float radius_local;
XMStoreFloat3(&center_local, XMVector3Transform(XMLoadFloat3(&sphere.center), objectMatInverse));
XMStoreFloat(&radius_local, XMVector3Length(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 intersection 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);
}
}
}
}
}
result.orientation = sphere.GetPlacementOrientation(result.position, result.normal);
return result;
}
Scene::CapsuleIntersectionResult Scene::Intersects(const Capsule& capsule, uint32_t filterMask, uint32_t layerMask, uint32_t lod) const
{
CapsuleIntersectionResult result;
const XMVECTOR Base = XMLoadFloat3(&capsule.base);
const XMVECTOR Tip = XMLoadFloat3(&capsule.tip);
const XMVECTOR Radius = XMVectorReplicate(capsule.radius);
const XMVECTOR Axis = XMVector3Normalize(Tip - Base);
const XMVECTOR LineEndOffset = Axis * 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())
{
collider_bvh.Intersects(capsule_aabb, 0, [&](uint32_t collider_index) {
const ColliderComponent& collider = colliders_cpu[collider_index];
if ((collider.layerMask & layerMask) == 0)
return;
float dist = 0;
XMFLOAT3 direction = {};
XMFLOAT3 position = {};
bool intersects = false;
switch (collider.shape)
{
default:
case ColliderComponent::Shape::Sphere:
intersects = capsule.intersects(collider.sphere, dist, direction);
XMStoreFloat3(&position, XMLoadFloat3(&collider.sphere.center) + XMLoadFloat3(&direction) * dist);
break;
case ColliderComponent::Shape::Capsule:
intersects = capsule.intersects(collider.capsule, position, direction, dist);
break;
case ColliderComponent::Shape::Plane:
intersects = capsule.intersects(collider.plane, dist, direction);
break;
}
if (intersects)
{
if (dist > result.depth)
{
result.depth = dist;
result.entity = colliders.GetEntity(collider_index);
result.normal = direction;
result.position = position;
result.velocity = {};
}
}
});
}
if (filterMask & FILTER_OBJECT_ALL)
{
for (size_t objectIndex = 0; objectIndex < aabb_objects.size(); ++objectIndex)
{
const AABB& aabb = aabb_objects[objectIndex];
if (capsule_aabb.intersects(aabb) == AABB::INTERSECTION_TYPE::OUTSIDE || (layerMask & aabb.layerMask) == 0)
continue;
const ObjectComponent& object = objects[objectIndex];
if (object.meshID == INVALID_ENTITY)
continue;
if ((filterMask & object.GetFilterMask()) == 0)
continue;
const MeshComponent* mesh = meshes.GetComponent(object.meshID);
if (mesh == nullptr)
continue;
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)
{
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];
XMVECTOR p0;
XMVECTOR p1;
XMVECTOR p2;
const bool softbody_active = softbody != nullptr && softbody->HasVertices();
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 = XMLoadFloat3(&mesh->vertex_positions[i0]);
p1 = XMLoadFloat3(&mesh->vertex_positions[i1]);
p2 = XMLoadFloat3(&mesh->vertex_positions[i2]);
}
else
{
p0 = SkinVertex(*mesh, *armature, i0);
p1 = SkinVertex(*mesh, *armature, i1);
p2 = SkinVertex(*mesh, *armature, i2);
}
}
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 (capsule_aabb.intersects(aabb_triangle) == AABB::OUTSIDE)
return;
// Compute the plane of the triangle (has to be normalized).
XMVECTOR N = XMVector3Normalize(XMVector3Cross(p1 - p0, p2 - p0));
XMVECTOR ReferencePoint;
XMVECTOR d = XMVector3Normalize(B - A);
if (std::abs(XMVectorGetX(XMVector3Dot(N, d))) < std::numeric_limits<float>::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(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(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);
bool onBackside = XMVectorGetX(Dist) > 0;
if (!mesh->IsDoubleSided() && onBackside)
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 lenX = XMVectorGetX(intersectionVecLen);
float depth = capsule.radius - lenX;
if (depth > result.depth)
{
result.entity = entity;
XMStoreFloat3(&result.position, bestPoint);
if (lenX > std::numeric_limits<float>::epsilon())
{
result.depth = depth;
XMStoreFloat3(&result.normal, intersectionVec / intersectionVecLen);
}
else
{
// The line segment that makes the spine of the capsule has
// intersected the triangle plane, so interSectionVec ~= Zero,
// and depth ~= capsule.radius. Use the triangle normal.
XMVECTOR CandNorm;
if (onBackside)
{
CandNorm = N;
} else
{
CandNorm = XMVectorNegate(N);
}
XMStoreFloat3(&result.normal, CandNorm);
// If the capsule has penetrated enough to intersect the spine, the
// depth is calculated from closest point on the spine, not from the
// actual endpoint, so the real depth may be greater, depending on the
// orientation of the capsule relative to the triangle normal.
// For simplicity, we assume the penetrating endpoint is the one closest
// to Center, and we project the distance from Center to the closest endpoint
// onto the normal.
XMVECTOR A_C = XMVector3LengthSq(Center - A);
XMVECTOR B_C = XMVector3LengthSq(Center - B);
XMVECTOR CDiff;
if (XMVector3Less(A_C, B_C))
{
CDiff = XMVectorSubtract(A, Center);
}
else
{
CDiff = XMVectorSubtract(B, Center);
}
XMVECTOR CDiffOnN = XMVectorMultiply(XMVector3Dot(CDiff, N), CDiff);
result.depth = depth + XMVectorGetX(XMVector3Length(CDiffOnN));
}
XMVECTOR vel = bestPoint - XMVector3Transform(XMVector3Transform(bestPoint, objectMat_Inverse), objectMatPrev);
XMStoreFloat3(&result.velocity, vel);
result.subsetIndex = (int)subsetIndex;
}
}
};
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, XMVector3Length(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 intersection 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);
}
}
}
}
}
result.orientation = capsule.GetPlacementOrientation(result.position, result.normal);
return result;
}
void Scene::VoxelizeObject(size_t objectIndex, wi::VoxelGrid& grid, bool subtract, uint32_t lod)
{
if (objectIndex >= objects.GetCount() || objectIndex >= aabb_objects.size())
return;
if (aabb_objects[objectIndex].intersects(grid.get_aabb()) == wi::primitive::AABB::OUTSIDE)
return;
const ObjectComponent& object = objects[objectIndex];
const MeshComponent* mesh = meshes.GetComponent(object.meshID);
if (mesh == nullptr)
return;
const SoftBodyPhysicsComponent* softbody = softbodies.GetComponent(object.meshID);
const XMMATRIX objectMat = XMLoadFloat4x4(&matrix_objects[objectIndex]);
const ArmatureComponent* armature = mesh->IsSkinned() ? armatures.GetComponent(mesh->armatureID) : nullptr;
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)
{
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];
XMVECTOR p0;
XMVECTOR p1;
XMVECTOR p2;
const bool softbody_active = softbody != nullptr && softbody->HasVertices();
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 = XMLoadFloat3(&mesh->vertex_positions[i0]);
p1 = XMLoadFloat3(&mesh->vertex_positions[i1]);
p2 = XMLoadFloat3(&mesh->vertex_positions[i2]);
}
else
{
p0 = SkinVertex(*mesh, *armature, i0);
p1 = SkinVertex(*mesh, *armature, i1);
p2 = SkinVertex(*mesh, *armature, i2);
}
p0 = XMVector3Transform(p0, objectMat);
p1 = XMVector3Transform(p1, objectMat);
p2 = XMVector3Transform(p2, objectMat);
}
grid.inject_triangle(p0, p1, p2, subtract);
}
}
}
void Scene::VoxelizeScene(wi::VoxelGrid& voxelgrid, bool subtract, uint32_t filterMask, uint32_t layerMask, uint32_t lod)
{
wi::jobsystem::context ctx;
if ((filterMask & FILTER_COLLIDER))
{
for (size_t i = 0; i < collider_count_cpu; ++i)
{
const ColliderComponent& collider = colliders_cpu[i];
if ((collider.layerMask & layerMask) == 0)
continue;
switch (collider.shape)
{
default:
case ColliderComponent::Shape::Sphere:
{
Sphere sphere = collider.sphere;
// TODO: fix heap allocating lambda capture!
wi::jobsystem::Execute(ctx, [&voxelgrid, subtract, sphere](wi::jobsystem::JobArgs args) {
voxelgrid.inject_sphere(sphere, subtract);
});
}
break;
case ColliderComponent::Shape::Capsule:
{
Capsule capsule = collider.capsule;
// TODO: fix heap allocating lambda capture!
wi::jobsystem::Execute(ctx, [&voxelgrid, subtract, capsule](wi::jobsystem::JobArgs args) {
voxelgrid.inject_capsule(capsule, subtract);
});
}
break;
case ColliderComponent::Shape::Plane:
{
XMMATRIX planeMatrix = XMMatrixInverse(nullptr, XMLoadFloat4x4(&collider.plane.projection));
XMVECTOR P0 = XMVector3Transform(XMVectorSet(-1, 0, -1, 1), planeMatrix);
XMVECTOR P1 = XMVector3Transform(XMVectorSet(1, 0, -1, 1), planeMatrix);
XMVECTOR P2 = XMVector3Transform(XMVectorSet(1, 0, 1, 1), planeMatrix);
XMVECTOR P3 = XMVector3Transform(XMVectorSet(-1, 0, 1, 1), planeMatrix);
// TODO: fix heap allocating lambda capture!
wi::jobsystem::Execute(ctx, [&voxelgrid, subtract, P0, P1, P2, P3](wi::jobsystem::JobArgs args) {
voxelgrid.inject_triangle(P0, P1, P2, subtract);
voxelgrid.inject_triangle(P0, P2, P3, subtract);
});
}
break;
}
}
}
if (filterMask & FILTER_OBJECT_ALL)
{
for (size_t i = 0; i < objects.GetCount(); ++i)
{
const ObjectComponent& object = objects[i];
if ((filterMask & object.GetFilterMask()) == 0)
continue;
const AABB& aabb = aabb_objects[i];
if ((layerMask & aabb.layerMask) == 0)
continue;
// TODO: fix heap allocating lambda capture!
wi::jobsystem::Execute(ctx, [this, &voxelgrid, subtract, lod, i](wi::jobsystem::JobArgs args) {
VoxelizeObject(i, voxelgrid, subtract, lod);
});
}
}
wi::jobsystem::Wait(ctx);
}
XMFLOAT3 Scene::GetPositionOnSurface(wi::ecs::Entity objectEntity, int vertexID0, int vertexID1, int vertexID2, const XMFLOAT2& bary) const
{
const ObjectComponent* object = objects.GetComponent(objectEntity);
if (object == nullptr || object->meshID == INVALID_ENTITY)
return XMFLOAT3(0, 0, 0);
const MeshComponent* mesh = meshes.GetComponent(object->meshID);
if (mesh == nullptr)
return XMFLOAT3(0, 0, 0);
const SoftBodyPhysicsComponent* softbody = softbodies.GetComponent(object->meshID);
const ArmatureComponent* armature = mesh->IsSkinned() ? armatures.GetComponent(mesh->armatureID) : nullptr;
XMVECTOR p0;
XMVECTOR p1;
XMVECTOR p2;
const bool softbody_active = softbody != nullptr && softbody->HasVertices();
if (softbody_active)
{
p0 = softbody->vertex_positions_simulation[vertexID0].LoadPOS();
p1 = softbody->vertex_positions_simulation[vertexID1].LoadPOS();
p2 = softbody->vertex_positions_simulation[vertexID2].LoadPOS();
}
else
{
if (armature == nullptr || armature->boneData.empty())
{
p0 = XMLoadFloat3(&mesh->vertex_positions[vertexID0]);
p1 = XMLoadFloat3(&mesh->vertex_positions[vertexID1]);
p2 = XMLoadFloat3(&mesh->vertex_positions[vertexID2]);
}
else
{
p0 = SkinVertex(*mesh, *armature, vertexID0);
p1 = SkinVertex(*mesh, *armature, vertexID1);
p2 = SkinVertex(*mesh, *armature, vertexID2);
}
}
XMVECTOR P = XMVectorBaryCentric(p0, p1, p2, bary.x, bary.y);
if (!softbody_active)
{
const size_t objectIndex = objects.GetIndex(objectEntity);
const XMMATRIX objectMat = XMLoadFloat4x4(&matrix_objects[objectIndex]);
P = XMVector3Transform(P, objectMat);
}
XMFLOAT3 result;
XMStoreFloat3(&result, P);
return result;
}
void Scene::PutWaterRipple(const std::string& image, const XMFLOAT3& pos)
{
wi::Sprite img(image);
img.params.enableExtractNormalMap();
img.params.blendFlag = BLENDMODE_ADDITIVE;
img.anim.fad = 0.01f;
img.anim.scaleX = 0.1f;
img.anim.scaleY = 0.1f;
img.params.pos = pos;
img.params.rotation = (wi::random::GetRandom(0, 1000) * 0.001f) * 2 * 3.1415f;
img.params.siz = XMFLOAT2(1, 1);
img.params.quality = wi::image::QUALITY_ANISOTROPIC;
img.params.pivot = XMFLOAT2(0.5f, 0.5f);
waterRipples.push_back(img);
}
void Scene::PutWaterRipple(const XMFLOAT3& pos)
{
wi::Sprite img;
img.textureResource.SetTexture(*wi::texturehelper::getWaterRipple());
img.params.enableExtractNormalMap();
img.params.blendFlag = BLENDMODE_ADDITIVE;
img.anim.fad = 0.01f;
img.anim.scaleX = 0.1f;
img.anim.scaleY = 0.1f;
img.params.pos = pos;
img.params.rotation = (wi::random::GetRandom(0, 1000) * 0.001f) * 2 * 3.1415f;
img.params.siz = XMFLOAT2(1, 1);
img.params.quality = wi::image::QUALITY_ANISOTROPIC;
img.params.pivot = XMFLOAT2(0.5f, 0.5f);
waterRipples.push_back(img);
}
XMVECTOR SkinVertex(const MeshComponent& mesh, const ArmatureComponent& armature, uint32_t index, XMVECTOR* N)
{
XMVECTOR P = XMLoadFloat3(&mesh.vertex_positions[index]);
const XMUINT4& ind = mesh.vertex_boneindices[index];
const XMFLOAT4& wei = mesh.vertex_boneweights[index];
const XMFLOAT4X4 mat[] = {
armature.boneData[ind.x].GetMatrix(),
armature.boneData[ind.y].GetMatrix(),
armature.boneData[ind.z].GetMatrix(),
armature.boneData[ind.w].GetMatrix(),
};
const XMMATRIX M[] = {
XMMatrixTranspose(XMLoadFloat4x4(&mat[0])),
XMMatrixTranspose(XMLoadFloat4x4(&mat[1])),
XMMatrixTranspose(XMLoadFloat4x4(&mat[2])),
XMMatrixTranspose(XMLoadFloat4x4(&mat[3])),
};
XMVECTOR skinned;
skinned = XMVector3Transform(P, M[0]) * wei.x;
skinned += XMVector3Transform(P, M[1]) * wei.y;
skinned += XMVector3Transform(P, M[2]) * wei.z;
skinned += XMVector3Transform(P, M[3]) * wei.w;
P = skinned;
if (N != nullptr)
{
*N = XMLoadFloat3(&mesh.vertex_normals[index]);
skinned = XMVector3TransformNormal(*N, M[0]) * wei.x;
skinned += XMVector3TransformNormal(*N, M[1]) * wei.y;
skinned += XMVector3TransformNormal(*N, M[2]) * wei.z;
skinned += XMVector3TransformNormal(*N, M[3]) * wei.w;
*N = XMVector3Normalize(skinned);
}
return P;
}
Entity LoadModel(const std::string& fileName, const XMMATRIX& transformMatrix, bool attached)
{
Entity rootEntity = INVALID_ENTITY;
if (attached)
{
rootEntity = CreateEntity();
}
LoadModel2(fileName, transformMatrix, rootEntity);
return rootEntity;
}
Entity LoadModel(Scene& scene, const std::string& fileName, const XMMATRIX& transformMatrix, bool attached)
{
Entity rootEntity = INVALID_ENTITY;
if (attached)
{
rootEntity = CreateEntity();
}
LoadModel2(scene, fileName, transformMatrix, rootEntity);
return rootEntity;
}
void LoadModel2(const std::string& fileName, const XMMATRIX& transformMatrix, Entity rootEntity)
{
Scene scene;
LoadModel(scene, fileName, transformMatrix, rootEntity);
GetScene().Merge(scene);
}
void LoadModel2(Scene& scene, const std::string& fileName, const XMMATRIX& transformMatrix, Entity rootEntity)
{
wi::Archive archive(fileName, true);
if (!archive.IsOpen())
return;
// Serialize it from file:
scene.Serialize(archive);
// First, create new root:
bool attached = true;
if (rootEntity == INVALID_ENTITY)
{
rootEntity = CreateEntity();
attached = false;
}
scene.transforms.Create(rootEntity);
scene.layers.Create(rootEntity).layerMask = ~0;
{
// Apply the optional transformation matrix to the new scene:
// Parent all unparented transforms to new root entity
for (size_t i = 0; i < scene.transforms.GetCount(); ++i)
{
Entity entity = scene.transforms.GetEntity(i);
if (entity != rootEntity && !scene.hierarchy.Contains(entity))
{
scene.Component_Attach(entity, rootEntity);
}
}
// The root component is transformed, scene is updated:
TransformComponent* root_transform = scene.transforms.GetComponent(rootEntity);
root_transform->MatrixTransform(transformMatrix);
scene.Update(0);
}
if (!attached)
{
// In this case, we don't care about the root anymore, so delete it. This will simplify overall hierarchy
scene.Component_DetachChildren(rootEntity);
scene.Entity_Remove(rootEntity);
}
}
PickResult Pick(const wi::primitive::Ray& ray, uint32_t filterMask, uint32_t layerMask, const Scene& scene, uint32_t lod)
{
return scene.Intersects(ray, filterMask, layerMask, lod);
}
SceneIntersectSphereResult SceneIntersectSphere(const wi::primitive::Sphere& sphere, uint32_t filterMask, uint32_t layerMask, const Scene& scene, uint32_t lod)
{
return scene.Intersects(sphere, filterMask, layerMask, lod);
}
SceneIntersectCapsuleResult SceneIntersectCapsule(const wi::primitive::Capsule& capsule, uint32_t filterMask, uint32_t layerMask, const Scene& scene, uint32_t lod)
{
return scene.Intersects(capsule, filterMask, layerMask, lod);
}
XMMATRIX Scene::ComputeParentMatrixRecursive(Entity entity) const
{
XMMATRIX parentMatrix = XMMatrixIdentity();
HierarchyComponent* hier = hierarchy.GetComponent(entity);
if (hier != nullptr)
{
Entity parentID = hier->parentID;
while (parentID != INVALID_ENTITY)
{
TransformComponent* transform_parent = transforms.GetComponent(parentID);
if (transform_parent == nullptr)
break;
parentMatrix *= transform_parent->GetLocalMatrix();
const HierarchyComponent* hier_recursive = hierarchy.GetComponent(parentID);
if (hier_recursive != nullptr)
{
parentID = hier_recursive->parentID;
}
else
{
parentID = INVALID_ENTITY;
}
}
}
return parentMatrix;
}
Entity Scene::RetargetAnimation(Entity dst, Entity src, bool bake_data, const Scene* src_scene)
{
if (src_scene == nullptr)
src_scene = this;
const AnimationComponent* animation_source = src_scene->animations.GetComponent(src);
if (animation_source == nullptr)
return INVALID_ENTITY;
const HumanoidComponent* humanoid_dest = humanoids.GetComponent(dst);
if (humanoid_dest == nullptr)
return INVALID_ENTITY;
bool retarget_valid = false;
Scene retarget_scene;
Entity retarget_entity = CreateEntity();
AnimationComponent& animation = retarget_scene.animations.Create(retarget_entity);
animation = *animation_source;
animation.channels.clear();
animation.samplers.clear();
animation.retargets.clear();
for (auto& channel : animation_source->channels)
{
bool found = false;
for (size_t i = 0; (i < src_scene->humanoids.GetCount()) && !found; ++i)
{
const HumanoidComponent& humanoid_source = src_scene->humanoids[i];
for (size_t humanoidBoneIndex = 0; humanoidBoneIndex < arraysize(humanoid_source.bones); ++humanoidBoneIndex)
{
Entity bone_source = humanoid_source.bones[humanoidBoneIndex];
if (bone_source == channel.target)
{
Entity bone_dest = humanoid_dest->bones[humanoidBoneIndex];
TransformComponent* transform_source = src_scene->transforms.GetComponent(bone_source);
TransformComponent* transform_dest = transforms.GetComponent(bone_dest);
if (transform_source != nullptr && transform_dest != nullptr)
{
retarget_valid = true;
found = true;
auto& retarget_channel = animation.channels.emplace_back();
retarget_channel = channel;
retarget_channel.target = bone_dest;
retarget_channel.samplerIndex = (int)animation.samplers.size();
auto& sampler = animation_source->samplers[channel.samplerIndex];
auto& retarget_sampler = animation.samplers.emplace_back();
retarget_sampler = sampler;
retarget_sampler.backwards_compatibility_data = {};
retarget_sampler.scene = src_scene == this ? nullptr : src_scene;
XMMATRIX srcParentMatrix = src_scene->ComputeParentMatrixRecursive(bone_source);
XMMATRIX srcMatrix = transform_source->GetLocalMatrix() * srcParentMatrix;
XMMATRIX inverseSrcMatrix = XMMatrixInverse(nullptr, srcMatrix);
XMMATRIX dstParentMatrix = ComputeParentMatrixRecursive(bone_dest);
XMMATRIX dstMatrix = transform_dest->GetLocalMatrix() * dstParentMatrix;
XMMATRIX inverseDstParentMatrix = XMMatrixInverse(nullptr, dstParentMatrix);
XMMATRIX dstRelativeMatrix = dstMatrix * inverseSrcMatrix;
XMMATRIX srcRelativeParentMatrix = srcParentMatrix * inverseDstParentMatrix;
if (bake_data)
{
// Create new animation data and bake the retargeted result into it:
Entity retarget_data_entity = CreateEntity();
auto& retarget_animation_data = retarget_scene.animation_datas.Create(retarget_data_entity);
retarget_sampler.data = retarget_data_entity;
retarget_scene.Component_Attach(retarget_data_entity, retarget_entity);
auto& animation_data = animation_datas.Contains(sampler.data) ? *animation_datas.GetComponent(sampler.data) : sampler.backwards_compatibility_data;
retarget_animation_data = animation_data;
XMVECTOR S, R, T; // matrix decompose destinations
switch (channel.path)
{
case AnimationComponent::AnimationChannel::Path::SCALE:
for (size_t offset = 0; offset < retarget_animation_data.keyframe_data.size(); offset += 3)
{
XMFLOAT3* data = (XMFLOAT3*)&retarget_animation_data.keyframe_data[offset];
TransformComponent transform = *transform_source;
transform.scale_local = *data;
XMMATRIX localMatrix = dstRelativeMatrix * transform.GetLocalMatrix() * srcRelativeParentMatrix;
XMMatrixDecompose(&S, &R, &T, localMatrix);
XMStoreFloat3(data, S);
}
break;
case AnimationComponent::AnimationChannel::Path::ROTATION:
for (size_t offset = 0; offset < retarget_animation_data.keyframe_data.size(); offset += 4)
{
XMFLOAT4* data = (XMFLOAT4*)&retarget_animation_data.keyframe_data[offset];
TransformComponent transform = *transform_source;
transform.rotation_local = *data;
XMMATRIX localMatrix = dstRelativeMatrix * transform.GetLocalMatrix() * srcRelativeParentMatrix;
XMMatrixDecompose(&S, &R, &T, localMatrix);
XMStoreFloat4(data, R);
}
break;
case AnimationComponent::AnimationChannel::Path::TRANSLATION:
for (size_t offset = 0; offset < retarget_animation_data.keyframe_data.size(); offset += 3)
{
XMFLOAT3* data = (XMFLOAT3*)&retarget_animation_data.keyframe_data[offset];
TransformComponent transform = *transform_source;
transform.translation_local = *data;
XMMATRIX localMatrix = dstRelativeMatrix * transform.GetLocalMatrix() * srcRelativeParentMatrix;
XMMatrixDecompose(&S, &R, &T, localMatrix);
XMStoreFloat3(data, T);
}
break;
default:
break;
}
}
else
{
// Don't bake retarget data, but inform the animation channel of original source data:
retarget_channel.retargetIndex = (int)animation.retargets.size();
AnimationComponent::RetargetSourceData& retarget = animation.retargets.emplace_back();
retarget.source = bone_source;
XMStoreFloat4x4(&retarget.dstRelativeMatrix, dstRelativeMatrix);
XMStoreFloat4x4(&retarget.srcRelativeParentMatrix, srcRelativeParentMatrix);
}
}
break;
}
}
}
}
if (retarget_valid)
{
retarget_scene.Component_Attach(retarget_entity, dst);
Merge(retarget_scene);
return retarget_entity;
}
return INVALID_ENTITY;
}
XMMATRIX Scene::GetRestPose(wi::ecs::Entity entity) const
{
if (entity != INVALID_ENTITY)
{
for (size_t i = 0; i < armatures.GetCount(); ++i)
{
const ArmatureComponent& armature = armatures[i];
int boneIndex = -1;
for (auto& x : armature.boneCollection)
{
boneIndex++;
if (x == entity)
{
XMMATRIX inverseBindMatrix = XMLoadFloat4x4(armature.inverseBindMatrices.data() + boneIndex);
XMMATRIX bindMatrix = XMMatrixInverse(nullptr, inverseBindMatrix);
return bindMatrix;
}
}
}
const TransformComponent* transform = transforms.GetComponent(entity);
if (transform != nullptr)
{
return XMLoadFloat4x4(&transform->world);
}
}
return XMMatrixIdentity();
}
float Scene::GetHumanoidDefaultFacing(const HumanoidComponent& humanoid, Entity humanoidEntity) const
{
Entity left_shoulder = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::LeftUpperArm];
Entity right_shoulder = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::RightUpperArm];
XMVECTOR left_shoulder_pos = GetRestPose(left_shoulder).r[3];
XMVECTOR right_shoulder_pos = GetRestPose(right_shoulder).r[3];
const TransformComponent* transform = transforms.GetComponent(humanoidEntity);
if (transform != nullptr)
{
XMVECTOR S = transform->GetScaleV();
left_shoulder_pos *= S;
right_shoulder_pos *= S;
}
if (XMVectorGetX(right_shoulder_pos) < XMVectorGetX(left_shoulder_pos))
{
return -1;
}
return 1;
}
void Scene::ScanAnimationDependencies()
{
if (animations.GetCount() == 0)
{
animation_queue_count = 0;
return;
}
animation_queues.reserve(animations.GetCount());
animation_queue_count = 0;
wi::jobsystem::Execute(animation_dependency_scan_workload, [&](wi::jobsystem::JobArgs args) {
auto range = wi::profiler::BeginRangeCPU("Animation Dependencies");
for (size_t i = 0; i < animations.GetCount(); ++i)
{
AnimationComponent& animationA = animations[i];
if (!animationA.IsPlaying() && animationA.last_update_time == animationA.timer)
{
continue;
}
bool dependency = false;
for (size_t queue_index = 0; queue_index < animation_queue_count; ++queue_index)
{
AnimationQueue& queue = animation_queues[queue_index];
for (auto& channelA : animationA.channels)
{
if (dependency)
{
// If dependency has been found, record all other entities in this animation too:
queue.entities.insert(channelA.target);
}
else if (queue.entities.find(channelA.target) != queue.entities.end())
{
// If two animations target the same entity, they have a dependency and need to be executed in order:
dependency = true;
queue.animations.push_back(&animationA);
}
}
if (dependency) break;
}
if (!dependency)
{
// No dependency, it can be executed on a separate queue (thread)
if (animation_queues.size() <= animation_queue_count)
{
animation_queues.resize(animation_queue_count + 1);
}
AnimationQueue& queue = animation_queues[animation_queue_count];
queue.animations.clear();
queue.animations.push_back(&animationA);
queue.entities.clear();
for (auto& channelA : animationA.channels)
{
queue.entities.insert(channelA.target);
}
animation_queue_count++;
}
}
wi::profiler::EndRange(range);
});
// We don't wait for this job here, it will be waited just before animation update
}
void Scene::ScanSpringDependencies()
{
wi::jobsystem::Execute(spring_dependency_scan_workload, [this](wi::jobsystem::JobArgs args){
auto range = wi::profiler::BeginRangeCPU("Spring Dependencies");
spring_queues.clear();
// First, reset all spring temp state:
for (size_t i = 0; i < springs.GetCount(); ++i)
{
SpringComponent& spring = springs[i];
spring.children.clear();
spring.entity = INVALID_ENTITY;
spring.transform = nullptr;
spring.parent_transform = nullptr;
}
// Then determine dependencies and set temp values:
for (size_t i = 0; i < springs.GetCount(); ++i)
{
SpringComponent& spring = springs[i];
if (spring.IsDisabled())
continue;
Entity entity = springs.GetEntity(i);
TransformComponent* transform = transforms.GetComponent(entity);
if (transform == nullptr)
continue;
spring.entity = entity;
spring.transform = transform;
const HierarchyComponent* hier = hierarchy.GetComponent(entity);
if (hier == nullptr)
{
// This is a root spring
spring_queues.push_back(&spring);
}
else
{
spring.parent_transform = transforms.GetComponent(hier->parentID);
SpringComponent* parent = springs.GetComponent(hier->parentID);
if (parent == nullptr)
{
// This is a root spring
spring_queues.push_back(&spring);
}
else
{
// This has a parent
parent->children.push_back(&spring);
}
}
}
wi::profiler::EndRange(range);
});
// We don't wait for this job here, it will be waited just before spring update
}
void Scene::UpdateSpringsTopDownRecursive(SpringComponent* parent_spring, SpringComponent& spring)
{
Entity entity = spring.entity;
TransformComponent& transform = *spring.transform;
if (spring.IsResetting())
{
spring.Reset(false);
// Note: the spring resetting works on the rest pose, not the current pose!
XMMATRIX parentWorldMatrix = XMMatrixIdentity();
{
const HierarchyComponent* hier = hierarchy.GetComponent(entity);
if (hier != nullptr)
{
parentWorldMatrix = GetRestPose(hier->parentID);
}
}
XMMATRIX parentWorldMatrixInverse = XMMatrixInverse(nullptr, parentWorldMatrix);
XMVECTOR position_root = GetRestPose(entity).r[3];
XMVECTOR tail = position_root + XMVectorSet(0, 1, 0, 0);
// Search for child to find the rest pose tail position:
bool child_found = false;
for (size_t j = 0; j < hierarchy.GetCount(); ++j)
{
const HierarchyComponent& hier = hierarchy[j];
Entity child = hierarchy.GetEntity(j);
if (hier.parentID == entity && transforms.Contains(child))
{
tail = GetRestPose(child).r[3];
child_found = true;
break;
}
}
if (!child_found && parent_spring != nullptr)
{
// No child, try to guess tail position compared to parent (if it has parent):
const XMVECTOR parent_pos = parentWorldMatrix.r[3];
const XMVECTOR ab = position_root - parent_pos;
tail = position_root + ab;
}
XMVECTOR axis = tail - position_root;
axis = XMVector3TransformNormal(axis, parentWorldMatrixInverse);
XMStoreFloat3(&spring.boneAxis, axis);
XMStoreFloat3(&spring.currentTail, tail);
spring.prevTail = spring.currentTail;
}
XMMATRIX parentWorldMatrix = XMMatrixIdentity();
if (spring.parent_transform != nullptr)
{
transform.UpdateTransform_Parented(*spring.parent_transform);
parentWorldMatrix = XMLoadFloat4x4(&spring.parent_transform->world);
}
XMVECTOR position_root = transform.GetPositionV();
// fixup spring locations by snapping position to parent's tail:
// (This is done after resetting code intentionally)
if (parent_spring != nullptr)
{
position_root = XMLoadFloat3(&parent_spring->currentTail);
}
XMVECTOR boneAxis = XMLoadFloat3(&spring.boneAxis);
boneAxis = XMVector3TransformNormal(boneAxis, parentWorldMatrix);
const float boneLength = XMVectorGetX(XMVector3Length(boneAxis));
boneAxis /= boneLength;
const float dragForce = spring.dragForce;
const float stiffnessForce = spring.stiffnessForce;
const XMVECTOR gravityDir = XMLoadFloat3(&spring.gravityDir);
const float gravityPower = spring.gravityPower;
const XMVECTOR tail_current = XMLoadFloat3(&spring.currentTail);
const XMVECTOR tail_prev = XMLoadFloat3(&spring.prevTail);
XMVECTOR inertia = (tail_current - tail_prev) * (1 - dragForce);
XMVECTOR stiffness = boneAxis * stiffnessForce;
XMVECTOR external = XMVectorZero();
if (spring.windForce > 0)
{
const XMVECTOR windDir = XMLoadFloat3(&weather.windDirection);
external += std::sin(time * weather.windSpeed + XMVectorGetX(XMVector3Dot(tail_current, windDir))) * windDir * spring.windForce;
}
if (spring.IsGravityEnabled())
{
external += gravityDir * gravityPower;
}
XMVECTOR tail_next = tail_current + inertia + dt * (stiffness + external);
XMVECTOR to_tail = XMVector3Normalize(tail_next - position_root);
// Limit offset to keep distance from parent:
tail_next = position_root + to_tail * boneLength;
#if 1
// Collider checks:
// apply scaling to radius:
XMFLOAT3 scale = transform.GetScale();
const float hitRadius = spring.hitRadius * std::max(scale.x, std::max(scale.y, scale.z));
wi::primitive::Sphere tail_sphere;
XMStoreFloat3(&tail_sphere.center, tail_next);
tail_sphere.radius = hitRadius;
if (colliders_cpu != nullptr)
{
collider_bvh.Intersects(tail_sphere, 0, [&](uint32_t collider_index) {
const ColliderComponent& collider = colliders_cpu[collider_index];
float dist = 0;
XMFLOAT3 direction = {};
switch (collider.shape)
{
default:
case ColliderComponent::Shape::Sphere:
tail_sphere.intersects(collider.sphere, dist, direction);
break;
case ColliderComponent::Shape::Capsule:
tail_sphere.intersects(collider.capsule, dist, direction);
break;
case ColliderComponent::Shape::Plane:
tail_sphere.intersects(collider.plane, dist, direction);
break;
}
if (dist < 0)
{
tail_next = tail_next - XMLoadFloat3(&direction) * dist;
to_tail = XMVector3Normalize(tail_next - position_root);
// Limit offset to keep distance from parent:
tail_next = position_root + to_tail * boneLength;
XMStoreFloat3(&tail_sphere.center, tail_next);
tail_sphere.radius = hitRadius;
}
});
}
#endif
XMStoreFloat3(&spring.prevTail, tail_current);
XMStoreFloat3(&spring.currentTail, tail_next);
// Rotate to face tail position:
const XMVECTOR axis = XMVector3Normalize(XMVector3Cross(boneAxis, to_tail));
const float angle = XMScalarACos(XMVectorGetX(XMVector3Dot(boneAxis, to_tail)));
const XMVECTOR Q = XMQuaternionNormalize(XMQuaternionRotationNormal(axis, angle));
// Modify world matrix:
XMMATRIX M = XMLoadFloat4x4(&transform.world);
XMVECTOR S, R, T;
XMMatrixDecompose(&S, &R, &T, M);
T = position_root;
R = XMQuaternionMultiply(R, Q);
R = XMQuaternionNormalize(R);
M = XMMatrixScalingFromVector(S) * XMMatrixRotationQuaternion(R) * XMMatrixTranslationFromVector(T);
XMStoreFloat4x4(&transform.world, M);
#if 0
// Debug axis:
static wi::SpinLock dbglocker;
wi::renderer::RenderableLine line;
line.color_start = line.color_end = XMFLOAT4(1, 1, 0, 1);
XMStoreFloat3(&line.start, position_root);
line.end = spring.currentTail;
dbglocker.lock();
wi::renderer::DrawLine(line);
dbglocker.unlock();
#endif
for (SpringComponent* child : spring.children)
{
UpdateSpringsTopDownRecursive(&spring, *child);
}
}
}