From c6ef1a65539967b3e548a6e61229df225f4503e7 Mon Sep 17 00:00:00 2001 From: turanszkij Date: Fri, 19 Oct 2018 18:51:43 +0100 Subject: [PATCH] updated physics, skinning, systems; added softbody to physics test --- Editor/MeshWindow.cpp | 5 + WickedEngine/skinningCS.hlsl | 3 - WickedEngine/wiGPUBVH.cpp | 4 +- WickedEngine/wiPhysicsEngine_Bullet.cpp | 118 +++++++++++++-------- WickedEngine/wiRenderer.cpp | 60 ++++++++--- WickedEngine/wiSceneSystem.cpp | 55 +++++++--- WickedEngine/wiSceneSystem.h | 24 +++-- WickedEngine/wiSceneSystem_Serializers.cpp | 8 +- WickedEngine/wiVersion.cpp | 2 +- models/physics_test.wiscene | Bin 16438 -> 65176 bytes 10 files changed, 188 insertions(+), 91 deletions(-) diff --git a/Editor/MeshWindow.cpp b/Editor/MeshWindow.cpp index b5d71c885..76fcfeb86 100644 --- a/Editor/MeshWindow.cpp +++ b/Editor/MeshWindow.cpp @@ -266,9 +266,14 @@ void MeshWindow::SetEntity(Entity entity) } tessellationFactorSlider->SetValue(mesh->GetTessellationFactor()); + softbodyCheckBox->SetEnabled(false); + massSlider->SetValue(0); + frictionSlider->SetValue(0); + SoftBodyPhysicsComponent* physicscomponent = wiRenderer::GetScene().softbodies.GetComponent(entity); if (physicscomponent != nullptr) { + softbodyCheckBox->SetEnabled(true); massSlider->SetValue(physicscomponent->mass); frictionSlider->SetValue(physicscomponent->friction); } diff --git a/WickedEngine/skinningCS.hlsl b/WickedEngine/skinningCS.hlsl index 26df8861d..ccee6262b 100644 --- a/WickedEngine/skinningCS.hlsl +++ b/WickedEngine/skinningCS.hlsl @@ -20,8 +20,6 @@ RAWBUFFER(vertexBuffer_POS, SKINNINGSLOT_IN_VERTEX_POS); RAWBUFFER(vertexBuffer_BON, SKINNINGSLOT_IN_VERTEX_BON); RWRAWBUFFER(streamoutBuffer_POS, SKINNINGSLOT_OUT_VERTEX_POS); -RWRAWBUFFER(streamoutBuffer_PRE, SKINNINGSLOT_OUT_VERTEX_PRE); - inline void Skinning(inout float3 pos, inout float3 nor, in float4 inBon, in float4 inWei) @@ -131,6 +129,5 @@ void main(uint3 DTid : SV_DispatchThreadID, uint3 GTid : SV_GroupThreadID) } // Store data: - streamoutBuffer_PRE.Store4(fetchAddress_POS_NOR, streamoutBuffer_POS.Load4(fetchAddress_POS_NOR)); // copy prev frame current pos to current frame prev pos streamoutBuffer_POS.Store4(fetchAddress_POS_NOR, pos_nor_u); } \ No newline at end of file diff --git a/WickedEngine/wiGPUBVH.cpp b/WickedEngine/wiGPUBVH.cpp index 73fa93475..ad00f1c2d 100644 --- a/WickedEngine/wiGPUBVH.cpp +++ b/WickedEngine/wiGPUBVH.cpp @@ -261,11 +261,9 @@ void wiGPUBVH::Build(const Scene& scene, GRAPHICSTHREAD threadID) if (object.meshID != INVALID_ENTITY) { const MeshComponent& mesh = *scene.meshes.GetComponent(object.meshID); - Entity entity = scene.objects.GetEntity(i); - const TransformComponent& transform = scene.transforms[object.transformComponentIndex]; BVHCB cb; - cb.xTraceBVHWorld = transform.world; + cb.xTraceBVHWorld = object.transform_index >= 0 ? scene.transforms[object.transform_index].world : IDENTITYMATRIX; cb.xTraceBVHMaterialOffset = materialCount; cb.xTraceBVHMeshTriangleOffset = triangleCount; cb.xTraceBVHMeshTriangleCount = (uint)mesh.indices.size() / 3; diff --git a/WickedEngine/wiPhysicsEngine_Bullet.cpp b/WickedEngine/wiPhysicsEngine_Bullet.cpp index 4cf89dd52..211c3ade0 100644 --- a/WickedEngine/wiPhysicsEngine_Bullet.cpp +++ b/WickedEngine/wiPhysicsEngine_Bullet.cpp @@ -198,24 +198,29 @@ namespace wiPhysicsEngine physicscomponent.physicsobject = body; } } - void AddSoftBody(Entity entity, wiSceneSystem::SoftBodyPhysicsComponent& physicscomponent, const wiSceneSystem::MeshComponent& mesh, const wiSceneSystem::TransformComponent& transform) + void AddSoftBody(Entity entity, wiSceneSystem::SoftBodyPhysicsComponent& physicscomponent, const wiSceneSystem::MeshComponent& mesh) { - btVector3 S = btVector3(transform.scale_local.x, transform.scale_local.y, transform.scale_local.z); - btQuaternion R = btQuaternion(transform.rotation_local.x, transform.rotation_local.y, transform.rotation_local.z, transform.rotation_local.z); - btVector3 T = btVector3(transform.translation_local.x, transform.translation_local.y, transform.translation_local.z); - - if (physicscomponent.physicsvertices.empty()) + if (physicscomponent.physicsToGraphicsVertexMapping.empty()) { physicscomponent.CreateFromMesh(mesh); } - const int vCount = (int)physicscomponent.physicsvertices.size(); + XMMATRIX worldMatrix = XMLoadFloat4x4(&physicscomponent.worldMatrix); + + const int vCount = (int)physicscomponent.physicsToGraphicsVertexMapping.size(); btScalar* btVerts = new btScalar[vCount * 3]; for (int i = 0; i < vCount; ++i) { - btVerts[i * 3 + 0] = btScalar(physicscomponent.physicsvertices[i].x); - btVerts[i * 3 + 1] = btScalar(physicscomponent.physicsvertices[i].y); - btVerts[i * 3 + 2] = btScalar(physicscomponent.physicsvertices[i].z); + uint32_t graphicsInd = physicscomponent.physicsToGraphicsVertexMapping[i]; + + XMFLOAT3 position = mesh.vertex_positions[graphicsInd]; + XMVECTOR P = XMLoadFloat3(&position); + P = XMVector3Transform(P, worldMatrix); + XMStoreFloat3(&position, P); + + btVerts[i * 3 + 0] = btScalar(position.x); + btVerts[i * 3 + 1] = btScalar(position.y); + btVerts[i * 3 + 2] = btScalar(position.z); } const int iCount = (int)mesh.indices.size(); @@ -253,20 +258,12 @@ namespace wiPhysicsEngine softBody->generateBendingConstraints(2, pm); softBody->randomizeConstraints(); - //btTransform shapeTransform; - //shapeTransform.setIdentity(); - //shapeTransform.setOrigin(T); - //shapeTransform.setRotation(R); - //softBody->scale(S); - //softBody->transform(shapeTransform); - - softBody->m_cfg.piterations = softbodyIterationCount; softBody->m_cfg.aeromodel = btSoftBody::eAeroModel::F_TwoSidedLiftDrag; softBody->m_cfg.kAHR = btScalar(.69); //0.69 Anchor hardness [0,1] softBody->m_cfg.kCHR = btScalar(1.0); //1 Rigid contact hardness [0,1] - softBody->m_cfg.kDF = btScalar(physicscomponent.friction); //0.2 Dynamic friction coefficient [0,1] + softBody->m_cfg.kDF = btScalar(0.2); //0.2 Dynamic friction coefficient [0,1] softBody->m_cfg.kDG = btScalar(0.01); //0 Drag coefficient [0,+inf] softBody->m_cfg.kDP = btScalar(0.0); //0 Damping coefficient [0,1] softBody->m_cfg.kKHR = btScalar(0.1); //0.1 Kinetic contact hardness [0,1] @@ -285,18 +282,20 @@ namespace wiPhysicsEngine softBody->m_cfg.kSS_SPLT_CL = btScalar(0.5); //0.5 Soft vs. rigid impulse split [0,1] - softBody->setTotalMass(physicscomponent.mass * physicscomponent.physicsvertices.size()); + softBody->setTotalMass(physicscomponent.mass); - for (size_t i = 0; i < physicscomponent.physicsvertices.size(); ++i) + for (size_t i = 0; i < physicscomponent.physicsToGraphicsVertexMapping.size(); ++i) { - softBody->setMass((int)i, physicscomponent.mass); + float weight = physicscomponent.weights[i]; + softBody->setMass((int)i, weight); } - //softBody->getCollisionShape()->setMargin(btScalar(0.2)); - softBody->setPose(true, true); - softBody->setActivationState(DISABLE_DEACTIVATION); + if (physicscomponent.IsDisableDeactivation()) + { + softBody->setActivationState(DISABLE_DEACTIVATION); + } ((btSoftRigidDynamicsWorld*)dynamicsWorld)->addSoftBody(softBody); physicscomponent.physicsobject = softBody; @@ -340,19 +339,21 @@ namespace wiPhysicsEngine // Try to register softbodies to Meshes: for (size_t i = 0; i < softbodies.GetCount(); ++i) { - Entity entity = softbodies.GetEntity(i); SoftBodyPhysicsComponent& physicscomponent = softbodies[i]; + Entity entity = softbodies.GetEntity(i); MeshComponent& mesh = *meshes.GetComponent(entity); mesh.SetDynamic(true); - if (physicscomponent.physicsobject == nullptr) + if (physicscomponent._flags & SoftBodyPhysicsComponent::SAFE_TO_REGISTER && physicscomponent.physicsobject == nullptr) { - TransformComponent transform; - AddSoftBody(entity, physicscomponent, mesh, transform); + AddSoftBody(entity, physicscomponent, mesh); } } - // Update all physics components and remove from simulation if components no longer exist: + // Perform internal simulation step: + dynamicsWorld->stepSimulation(dt, 10); + + // Synchronize scene entities with physics engine: for (int i = 0; i < dynamicsWorld->getCollisionObjectArray().size(); ++i) { btCollisionObject* collisionobject = dynamicsWorld->getCollisionObjectArray()[i]; @@ -375,10 +376,13 @@ namespace wiPhysicsEngine btMotionState* motionState = rigidbody->getMotionState(); btTransform physicsTransform; + // For kinematic object, system updates physics state, else the physics updates system state: if (physicscomponent->IsKinematic()) { - btVector3 T(transform.translation_local.x, transform.translation_local.y, transform.translation_local.z); - btQuaternion R(transform.rotation_local.x, transform.rotation_local.y, transform.rotation_local.z, transform.rotation_local.w); + XMFLOAT3 position = transform.GetPosition(); + XMFLOAT4 rotation = transform.GetRotation(); + btVector3 T(position.x, position.y, position.z); + btQuaternion R(rotation.x, rotation.y, rotation.z, rotation.w); physicsTransform.setOrigin(T); physicsTransform.setRotation(R); motionState->setWorldTransform(physicsTransform); @@ -417,30 +421,54 @@ namespace wiPhysicsEngine softbody->getAabb(aabb_min, aabb_max); mesh.aabb = AABB(XMFLOAT3(aabb_min.x(), aabb_min.y(), aabb_min.z()), XMFLOAT3(aabb_max.x(), aabb_max.y(), aabb_max.z())); + // This is different from rigid bodies, because soft body is a per mesh component. World matrix is propagated down from single mesh instance (ObjectUpdateSystem). + XMMATRIX worldMatrix = XMLoadFloat4x4(&physicscomponent->worldMatrix); + + // System can control zero weight soft body nodes: + for (size_t ind = 0; ind < physicscomponent->weights.size(); ++ind) + { + float weight = physicscomponent->weights[ind]; + + if (weight == 0) + { + btSoftBody::Node& node = softbody->m_nodes[(uint32_t)ind]; + uint32_t graphicsInd = physicscomponent->physicsToGraphicsVertexMapping[ind]; + XMFLOAT3 position = mesh.vertex_positions[graphicsInd]; + XMVECTOR P = XMLoadFloat3(&position); + P = XMVector3Transform(P, worldMatrix); + XMStoreFloat3(&position, P); + node.m_x = btVector3(position.x, position.y, position.z); + } + } + + // Soft body simulation nodes will update graphics mesh: for (size_t ind = 0; ind < mesh.vertex_positions.size(); ++ind) { uint32_t physicsInd = physicscomponent->graphicsToPhysicsVertexMapping[ind]; - btSoftBody::Node& node = softbody->m_nodes[physicsInd]; + float weight = physicscomponent->weights[physicsInd]; - XMFLOAT3& position = mesh.vertex_positions[ind]; - position.x = node.m_x.getX(); - position.y = node.m_x.getY(); - position.z = node.m_x.getZ(); - - if (!mesh.vertex_normals.empty()) + if (weight > 0) { - XMFLOAT3& normal = mesh.vertex_normals[ind]; - normal.x = -node.m_n.getX(); - normal.y = -node.m_n.getY(); - normal.z = -node.m_n.getZ(); + btSoftBody::Node& node = softbody->m_nodes[physicsInd]; + + XMFLOAT3& position = mesh.vertex_positions[ind]; + position.x = node.m_x.getX(); + position.y = node.m_x.getY(); + position.z = node.m_x.getZ(); + + if (!mesh.vertex_normals.empty()) + { + XMFLOAT3& normal = mesh.vertex_normals[ind]; + normal.x = -node.m_n.getX(); + normal.y = -node.m_n.getY(); + normal.z = -node.m_n.getZ(); + } } } } } } - dynamicsWorld->stepSimulation(dt, 10); - wiProfiler::GetInstance().EndRange(); // Physics } } diff --git a/WickedEngine/wiRenderer.cpp b/WickedEngine/wiRenderer.cpp index 30063df17..f782e3940 100644 --- a/WickedEngine/wiRenderer.cpp +++ b/WickedEngine/wiRenderer.cpp @@ -1433,21 +1433,19 @@ void RenderMeshes(const RenderQueue& renderQueue, SHADERTYPE shaderType, UINT re instancedBatchArray[instancedBatchCount - 1].forceAlphatestForDithering = 1; } - const TransformComponent& transform = scene.transforms[instance.transformComponentIndex]; + const XMFLOAT4X4& worldMatrix = instance.transform_index >= 0 ? scene.transforms[instance.transform_index].world : IDENTITYMATRIX; // Write into actual GPU-buffer: if (advancedVBRequest) { - ((volatile InstBuf*)instances)[batchID].instance.Create(transform.world, instance.color, dither); + ((volatile InstBuf*)instances)[batchID].instance.Create(worldMatrix, instance.color, dither); - // The following GetComponent() is going to be a map lookup in this hot loop, but ideally just once per frame per object. Might need to optimize later if it becomes a problem: - Entity objectEntity = scene.objects.GetEntity(instanceIndex); - const PreviousFrameTransformComponent& prev_transform = *scene.prev_transforms.GetComponent(objectEntity); - ((volatile InstBuf*)instances)[batchID].instancePrev.Create(prev_transform.world_prev); + const XMFLOAT4X4& prev_worldMatrix = instance.prev_transform_index >= 0 ? scene.prev_transforms[instance.prev_transform_index].world_prev : IDENTITYMATRIX; + ((volatile InstBuf*)instances)[batchID].instancePrev.Create(prev_worldMatrix); } else { - ((volatile Instance*)instances)[batchID].Create(transform.world, instance.color, dither); + ((volatile Instance*)instances)[batchID].Create(worldMatrix, instance.color, dither); } instancedBatchArray[instancedBatchCount - 1].instanceCount++; // next instance in current InstancedBatch @@ -1608,7 +1606,7 @@ void RenderMeshes(const RenderQueue& renderQueue, SHADERTYPE shaderType, UINT re GPUBuffer* vbs[] = { mesh.streamoutBuffer_POS.get() != nullptr ? mesh.streamoutBuffer_POS.get() : mesh.vertexBuffer_POS.get(), mesh.vertexBuffer_TEX.get(), - mesh.streamoutBuffer_PRE.get() != nullptr ? mesh.streamoutBuffer_PRE.get() : mesh.vertexBuffer_POS.get(), + mesh.vertexBuffer_PRE.get() != nullptr ? mesh.vertexBuffer_PRE.get() : mesh.vertexBuffer_POS.get(), &dynamicVertexBufferPools[threadID] }; UINT strides[] = { @@ -3367,10 +3365,43 @@ void SetUpStates() void UpdatePerFrameData(float dt) { + GraphicsDevice* device = GetDevice(); Scene& scene = GetScene(); scene.Update(dt * GetGameSpeed()); + // Need to swap prev and current vertex buffers for any dynamic meshes BEFORE render threads are kicked: + { + for (size_t i = 0; i < scene.meshes.GetCount(); ++i) + { + MeshComponent& mesh = scene.meshes[i]; + + if (mesh.IsSkinned() && scene.armatures.Contains(mesh.armatureID)) + { + if (mesh.vertexBuffer_PRE == nullptr) + { + mesh.vertexBuffer_PRE.reset(new GPUBuffer); + HRESULT hr = device->CreateBuffer(&mesh.streamoutBuffer_POS->GetDesc(), nullptr, mesh.vertexBuffer_PRE.get()); + assert(SUCCEEDED(hr)); + } + mesh.streamoutBuffer_POS.swap(mesh.vertexBuffer_PRE); + } + } + for (size_t i = 0; i < scene.softbodies.GetCount(); ++i) + { + Entity entity = scene.softbodies.GetEntity(i); + MeshComponent& mesh = *scene.meshes.GetComponent(entity); + + if (mesh.vertexBuffer_PRE == nullptr) + { + mesh.vertexBuffer_PRE.reset(new GPUBuffer); + HRESULT hr = device->CreateBuffer(&mesh.vertexBuffer_POS->GetDesc(), nullptr, mesh.vertexBuffer_PRE.get()); + assert(SUCCEEDED(hr)); + } + mesh.vertexBuffer_POS.swap(mesh.vertexBuffer_PRE); + } + } + // Update Voxelization parameters: if (scene.objects.GetCount() > 0) { @@ -3895,16 +3926,15 @@ void UpdateRenderData(GRAPHICSTHREAD threadID) mesh.vertexBuffer_POS.get(), mesh.vertexBuffer_BON.get(), }; - GPUResource* sos[] = { + GPUResource* so[] = { mesh.streamoutBuffer_POS.get(), - mesh.streamoutBuffer_PRE.get(), }; device->BindResources(CS, vbs, SKINNINGSLOT_IN_VERTEX_POS, ARRAYSIZE(vbs), threadID); - device->BindUAVs(CS, sos, 0, ARRAYSIZE(sos), threadID); + device->BindUAVs(CS, so, 0, ARRAYSIZE(so), threadID); device->Dispatch((UINT)ceilf((float)mesh.vertex_positions.size() / SKINNING_COMPUTE_THREADCOUNT), 1, 1, threadID); - device->UAVBarrier(sos, ARRAYSIZE(sos), threadID); // todo: defer, to gain from async compute + device->UAVBarrier(so, ARRAYSIZE(so), threadID); // todo: defer, to gain from async compute } } @@ -3925,6 +3955,7 @@ void UpdateRenderData(GRAPHICSTHREAD threadID) Entity entity = scene.softbodies.GetEntity(i); MeshComponent& mesh = *scene.meshes.GetComponent(entity); + // Copy new simulation data to vertex buffer const size_t vb_size = sizeof(MeshComponent::Vertex_POS) * mesh.vertex_positions.size(); MeshComponent::Vertex_POS* vb = (MeshComponent::Vertex_POS*)frameAllocators[threadID].allocate(vb_size); @@ -5943,7 +5974,7 @@ void RefreshImpostors(GRAPHICSTHREAD threadID) GPUBuffer* vbs[] = { mesh.IsSkinned() ? mesh.streamoutBuffer_POS.get() : mesh.vertexBuffer_POS.get(), mesh.vertexBuffer_TEX.get(), - mesh.IsSkinned() ? mesh.streamoutBuffer_PRE.get() : mesh.vertexBuffer_POS.get(), + mesh.vertexBuffer_POS.get(), &dynamicVertexBufferPools[threadID] }; UINT strides[] = { @@ -7719,8 +7750,7 @@ RayIntersectWorldResult RayIntersectWorld(const RAY& ray, UINT renderTypeMask, u if (layer.GetLayerMask() & layerMask) { const MeshComponent& mesh = *scene.meshes.GetComponent(object.meshID); - - const TransformComponent& transform = scene.transforms[object.transformComponentIndex]; + const TransformComponent& transform = scene.transforms[object.transform_index]; const XMMATRIX objectMat = XMLoadFloat4x4(&transform.world); const XMMATRIX objectMat_Inverse = XMMatrixInverse(nullptr, objectMat); diff --git a/WickedEngine/wiSceneSystem.cpp b/WickedEngine/wiSceneSystem.cpp index 28d4f1747..fdf01204e 100644 --- a/WickedEngine/wiSceneSystem.cpp +++ b/WickedEngine/wiSceneSystem.cpp @@ -395,11 +395,6 @@ namespace wiSceneSystem streamoutBuffer_POS.reset(new GPUBuffer); hr = wiRenderer::GetDevice()->CreateBuffer(&bd, nullptr, streamoutBuffer_POS.get()); assert(SUCCEEDED(hr)); - - bd.ByteWidth = (UINT)(sizeof(Vertex_POS) * vertex_positions.size()); - streamoutBuffer_PRE.reset(new GPUBuffer); - hr = wiRenderer::GetDevice()->CreateBuffer(&bd, nullptr, streamoutBuffer_PRE.get()); - assert(SUCCEEDED(hr)); } // vertexBuffer - TEXCOORDS @@ -471,6 +466,8 @@ namespace wiSceneSystem assert(SUCCEEDED(hr)); } + // vertexBuffer_PRE will be created on demand later! + } void MeshComponent::ComputeNormals(bool smooth) { @@ -750,8 +747,10 @@ namespace wiSceneSystem void SoftBodyPhysicsComponent::CreateFromMesh(const MeshComponent& mesh) { // Create a mapping that maps unique vertex positions to all vertex indices that share that. Unique vertex positions will make up the physics mesh: - std::unordered_map uniquePositions; + std::unordered_map uniquePositions; graphicsToPhysicsVertexMapping.resize(mesh.vertex_positions.size()); + physicsToGraphicsVertexMapping.clear(); + weights.clear(); for (size_t i = 0; i < mesh.vertex_positions.size(); ++i) { @@ -766,11 +765,14 @@ namespace wiSceneSystem if (uniquePositions.count(vertexHash) == 0) { - physicsvertices.push_back(position); - uniquePositions[vertexHash] = physicsvertices.size(); + uniquePositions[vertexHash] = (uint32_t)physicsToGraphicsVertexMapping.size(); + physicsToGraphicsVertexMapping.push_back((uint32_t)i); } - graphicsToPhysicsVertexMapping[i] = (uint32_t)uniquePositions[vertexHash]; + graphicsToPhysicsVertexMapping[i] = uniquePositions[vertexHash]; } + + weights.resize(physicsToGraphicsVertexMapping.size()); + std::fill(weights.begin(), weights.end(), 1.0f); } void CameraComponent::CreatePerspective(float newWidth, float newHeight, float newNear, float newFar, float newFOV) @@ -876,7 +878,7 @@ namespace wiSceneSystem RunImpostorUpdateSystem(impostors); - RunObjectUpdateSystem(transforms, meshes, materials, objects, aabb_objects, impostors, bounds, waterPlane); + RunObjectUpdateSystem(prev_transforms, transforms, meshes, materials, objects, aabb_objects, impostors, softbodies, bounds, waterPlane); RunCameraUpdateSystem(transforms, cameras); @@ -1562,12 +1564,14 @@ namespace wiSceneSystem } } void RunObjectUpdateSystem( + const wiECS::ComponentManager& prev_transforms, const ComponentManager& transforms, const ComponentManager& meshes, const ComponentManager& materials, ComponentManager& objects, ComponentManager& aabb_objects, ComponentManager& impostors, + ComponentManager& softbodies, AABB& sceneBounds, XMFLOAT4& waterPlane ) @@ -1591,16 +1595,17 @@ namespace wiSceneSystem if (object.meshID != INVALID_ENTITY) { Entity entity = objects.GetEntity(i); - - object.transformComponentIndex = (uint32_t)transforms.GetIndex(entity); - - const TransformComponent& transform = transforms[object.transformComponentIndex]; const MeshComponent* mesh = meshes.GetComponent(object.meshID); + // These will only be valid for a single frame: + object.transform_index = (int)transforms.GetIndex(entity); + object.prev_transform_index = (int)prev_transforms.GetIndex(entity); + + const TransformComponent& transform = transforms[object.transform_index]; + if (mesh != nullptr) { aabb = mesh->aabb.get(transform.world); - sceneBounds = AABB::Merge(sceneBounds, aabb); // This is instance bounding box matrix: XMFLOAT4X4 meshMatrix; @@ -1656,6 +1661,26 @@ namespace wiSceneSystem impostor->fadeThresholdRadius = object.impostorFadeThresholdRadius; impostor->instanceMatrices.push_back(meshMatrix); } + + SoftBodyPhysicsComponent* softBody = softbodies.GetComponent(object.meshID); + if (softBody != nullptr) + { + softBody->_flags |= SoftBodyPhysicsComponent::SAFE_TO_REGISTER; // this will be registered as soft body in the next frame + softBody->worldMatrix = transform.world; + + if (wiPhysicsEngine::IsEnabled() && softBody->physicsobject != nullptr) + { + // If physics engine is enabled and this object was registered, it will update soft body vertices in world space, so after that they no longer need to be transformed: + object.transform_index = -1; + object.prev_transform_index = -1; + + // mesh aabb will be used for soft bodies + aabb = mesh->aabb; + } + + } + + sceneBounds = AABB::Merge(sceneBounds, aabb); } } } diff --git a/WickedEngine/wiSceneSystem.h b/WickedEngine/wiSceneSystem.h index d17023b7a..6cf56b2ef 100644 --- a/WickedEngine/wiSceneSystem.h +++ b/WickedEngine/wiSceneSystem.h @@ -227,8 +227,8 @@ namespace wiSceneSystem std::unique_ptr vertexBuffer_BON; std::unique_ptr vertexBuffer_COL; std::unique_ptr vertexBuffer_ATL; + std::unique_ptr vertexBuffer_PRE; std::unique_ptr streamoutBuffer_POS; - std::unique_ptr streamoutBuffer_PRE; inline void SetRenderable(bool value) { if (value) { _flags |= RENDERABLE; } else { _flags &= ~RENDERABLE; } } @@ -412,8 +412,9 @@ namespace wiSceneSystem float impostorFadeThresholdRadius; float impostorSwapDistance; - // single frame-lifetime, to directly index transform component: - uint32_t transformComponentIndex; + // these will only be valid for a single frame: + int transform_index = -1; + int prev_transform_index = -1; // occlusion result history bitfield (32 bit->32 frame history) uint32_t occlusionHistory = ~0; @@ -489,17 +490,24 @@ namespace wiSceneSystem enum FLAGS { EMPTY = 0, - DISABLE_DEACTIVATION = 1 << 0, + SAFE_TO_REGISTER = 1 << 0, + DISABLE_DEACTIVATION = 1 << 1, }; - uint32_t _flags = EMPTY; + uint32_t _flags = DISABLE_DEACTIVATION; float mass = 1.0f; float friction = 1.0f; - std::vector physicsvertices; - std::vector graphicsToPhysicsVertexMapping; + std::vector physicsToGraphicsVertexMapping; // maps graphics vertex index to physics vertex index of the same position + std::vector graphicsToPhysicsVertexMapping; // maps a physics vertex index to first graphics vertex index of the same position + std::vector weights; // weight per physics vertex controlling the mass. (0: disable weight (no physics, only animation), 1: default weight) // Non-serialized attributes: void* physicsobject = nullptr; + XMFLOAT4X4 worldMatrix = IDENTITYMATRIX; + + inline void SetDisableDeactivation(bool value) { if (value) { _flags |= DISABLE_DEACTIVATION; } else { _flags &= ~DISABLE_DEACTIVATION; } } + + inline bool IsDisableDeactivation() const { return _flags & DISABLE_DEACTIVATION; } // Create physics represenation of graphics mesh void CreateFromMesh(const MeshComponent& mesh); @@ -999,12 +1007,14 @@ namespace wiSceneSystem void RunMaterialUpdateSystem(wiECS::ComponentManager& materials, float dt); void RunImpostorUpdateSystem(wiECS::ComponentManager& impostors); void RunObjectUpdateSystem( + const wiECS::ComponentManager& prev_transforms, const wiECS::ComponentManager& transforms, const wiECS::ComponentManager& meshes, const wiECS::ComponentManager& materials, wiECS::ComponentManager& objects, wiECS::ComponentManager& aabb_objects, wiECS::ComponentManager& impostors, + wiECS::ComponentManager& softbodies, AABB& sceneBounds, XMFLOAT4& waterPlane ); diff --git a/WickedEngine/wiSceneSystem_Serializers.cpp b/WickedEngine/wiSceneSystem_Serializers.cpp index 4cfeb68ec..b51355c37 100644 --- a/WickedEngine/wiSceneSystem_Serializers.cpp +++ b/WickedEngine/wiSceneSystem_Serializers.cpp @@ -267,16 +267,20 @@ namespace wiSceneSystem archive >> _flags; archive >> mass; archive >> friction; - archive >> physicsvertices; + archive >> physicsToGraphicsVertexMapping; archive >> graphicsToPhysicsVertexMapping; + archive >> weights; + + _flags &= ~SAFE_TO_REGISTER; } else { archive << _flags; archive << mass; archive << friction; - archive << physicsvertices; + archive << physicsToGraphicsVertexMapping; archive << graphicsToPhysicsVertexMapping; + archive << weights; } } void ArmatureComponent::Serialize(wiArchive& archive, uint32_t seed) diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index b0c075a5b..6eced5de9 100644 --- a/WickedEngine/wiVersion.cpp +++ b/WickedEngine/wiVersion.cpp @@ -9,7 +9,7 @@ namespace wiVersion // minor features, major updates const int minor = 21; // minor bug fixes, alterations, refactors, updates - const int revision = 13; + const int revision = 14; long GetVersion() diff --git a/models/physics_test.wiscene b/models/physics_test.wiscene index ab2ef1bed05687459535b1fb9c64d665a7ae341f..0debf8337668781bda870e1902344f1fe4efbff4 100644 GIT binary patch literal 65176 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 DgP|Dq delta 1421 zcmbR7mw8(QBc~Vx1V~O+>=NIopvF{hdip~oNbJ*_L%bmRPH&hSi2giNTpC0lxN}Jv zM2jv`i~`Y*gTG{f=+xK~wjla}albQ&)=TmY1JP>&ddfhwa7APwh}Pb~mj|M|7rDEG zXzzEX=^#31BbN`5@@4obSziefP`Z7$5JXFv{B{A+&r9=zLG(n;N?#D|^y_*ki0*t6 z0k&Z2HLeg4zf`{&>;OR^dvYyvPd&^hFdx8Nj_zV2U5Md8n8R>5hzsGO&9hj{nFrcE z^>Fv_gMtePU;zXR7+4h2CP0J{Q3SD`fq`XmW0%O}{alTc_i{B)w&MD&2up>q)CNmM zu*AT!`5`wuBMV5`<~KYOSXe*`H#Z16h_Qf_Za$)Zfu9AWc=8N$1shlzhNV?l+Jq$~ zm^)wr!VDDv>6<*^iw+A&BZBb(C;~D9ArkNvs-^+Pfa`hy6oD&-Ge9;^F8HRy0%k0L zF)qLuAnjnO313v;35GuLgNPnzaBSxOk;ypFj+p#iQE;*w7vE;1Ux#ozhCty0FNDy` z7+BVYl^w9M1Xi}dvN$wdOzz>~oP2?YV{(oF$K*YK5?I8yU$mI)z$wB5D`P-q%VdYY eHmCv<|2j{e0MxobkYnfh1<83_O=;dM3u