Skip to content

Commit

Permalink
occlusion raytest, ragdoll fatness, ragdoll headsize
Browse files Browse the repository at this point in the history
  • Loading branch information
turanszkij committed Mar 6, 2024
1 parent a83bf5c commit 305ca85
Show file tree
Hide file tree
Showing 11 changed files with 325 additions and 13 deletions.
5 changes: 5 additions & 0 deletions Content/Documentation/ScriptingAPI-Documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,7 @@ The scene holds components. Entity handles can be used to retrieve associated co
- [outer]FILTER_COLLIDER : uint -- include colliders
- [outer]FILTER_ALL : uint -- include everything
- Intersects(Ray|Sphere|Capsule primitive, opt uint filterMask = ~0u, opt uint layerMask = ~0u, opt uint lod = 0) : int entity, Vector position,normal, float distance, Vector velocity, int subsetIndex, Matrix orientation -- intersects a primitive with the scene and returns collision parameters
- IntersectsFirst(Ray primitive, opt uint filterMask = ~0u, opt uint layerMask = ~0u, opt uint lod = 0) : bool -- intersects a primitive with the scene and returns true immediately on intersection, false if there was no intersection. This can be faster for occlusion check than regular `Intersects` that searches for closest intersection.
- Update() -- updates the scene and every entity and component inside the scene
- Clear() -- deletes every entity and component inside the scene
- Merge(Scene other) -- moves contents from an other scene into this one. The other scene will be empty after this operation (contents are moved, not copied)
Expand Down Expand Up @@ -1301,6 +1302,10 @@ Describes a Collider object.
- SetLookAt(Vector value) -- Set a target lookAt position (for head an eyes movement)
- SetRagdollPhysicsEnabled(bool value) -- Activate dynamic ragdoll physics. Note that kinematic ragdoll physics is always active (ragdoll is animation-driven/kinematic by default).
- IsRagdollPhysicsEnabled() : bool
- SetRagdollFatness(float value) -- Control the overall fatness of the ragdoll body parts except head (default: 1)
- SetRagdollHeadSize(float value) -- Control the overall size of the ragdoll head (default: 1)
- GetRagdollFatness() : float
- GetRagdollHeadSize() : float

[outer] HumanoidBone = {
Hips = 0,
Expand Down
34 changes: 33 additions & 1 deletion Editor/HumanoidWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ void HumanoidWindow::Create(EditorComponent* _editor)
editor = _editor;

wi::gui::Window::Create(ICON_HUMANOID " Humanoid", wi::gui::Window::WindowControls::COLLAPSE | wi::gui::Window::WindowControls::CLOSE);
SetSize(XMFLOAT2(670, 540));
SetSize(XMFLOAT2(670, 580));

closeButton.SetTooltip("Delete HumanoidComponent");
OnClose([=](wi::gui::EventArgs args) {
Expand Down Expand Up @@ -168,6 +168,34 @@ void HumanoidWindow::Create(EditorComponent* _editor)
});
AddWidget(&headSizeSlider);

ragdollFatnessSlider.Create(0.5f, 2, 1, 1000, "Ragdoll fatness: ");
ragdollFatnessSlider.SetTooltip("Adjust overall fatness of ragdoll physics skeleton.");
ragdollFatnessSlider.SetSize(XMFLOAT2(wid, hei));
ragdollFatnessSlider.OnSlide([=](wi::gui::EventArgs args) {
wi::scene::Scene& scene = editor->GetCurrentScene();
HumanoidComponent* humanoid = scene.humanoids.GetComponent(entity);
if (humanoid != nullptr)
{
humanoid->ragdoll_fatness = args.fValue;
humanoid->ragdoll = {}; // request recreate
}
});
AddWidget(&ragdollFatnessSlider);

ragdollHeadSizeSlider.Create(0.5f, 2, 1, 1000, "Ragdoll head: ");
ragdollHeadSizeSlider.SetTooltip("Adjust overall size of ragdoll physics head.");
ragdollHeadSizeSlider.SetSize(XMFLOAT2(wid, hei));
ragdollHeadSizeSlider.OnSlide([=](wi::gui::EventArgs args) {
wi::scene::Scene& scene = editor->GetCurrentScene();
HumanoidComponent* humanoid = scene.humanoids.GetComponent(entity);
if (humanoid != nullptr)
{
humanoid->ragdoll_headsize = args.fValue;
humanoid->ragdoll = {}; // request recreate
}
});
AddWidget(&ragdollHeadSizeSlider);

boneList.Create("Bones: ");
boneList.SetSize(XMFLOAT2(wid, 200));
boneList.SetPos(XMFLOAT2(4, y += step));
Expand Down Expand Up @@ -241,6 +269,8 @@ void HumanoidWindow::SetEntity(Entity entity)
eyeRotMaxXSlider.SetValue(wi::math::RadiansToDegrees(humanoid->eye_rotation_max.x));
eyeRotMaxYSlider.SetValue(wi::math::RadiansToDegrees(humanoid->eye_rotation_max.y));
eyeRotSpeedSlider.SetValue(humanoid->eye_rotation_speed);
ragdollFatnessSlider.SetValue(humanoid->ragdoll_fatness);
ragdollHeadSizeSlider.SetValue(humanoid->ragdoll_headsize);

Entity bone = humanoid->bones[size_t(HumanoidComponent::HumanoidBone::Head)];
const TransformComponent* transform = scene.transforms.GetComponent(bone);
Expand Down Expand Up @@ -549,6 +579,8 @@ void HumanoidWindow::ResizeLayout()
add(eyeRotMaxYSlider);
add(eyeRotSpeedSlider);
add(headSizeSlider);
add(ragdollFatnessSlider);
add(ragdollHeadSizeSlider);

y += jump;

Expand Down
2 changes: 2 additions & 0 deletions Editor/HumanoidWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ class HumanoidWindow : public wi::gui::Window
wi::gui::Slider eyeRotMaxYSlider;
wi::gui::Slider eyeRotSpeedSlider;
wi::gui::Slider headSizeSlider;
wi::gui::Slider ragdollFatnessSlider;
wi::gui::Slider ragdollHeadSizeSlider;
wi::gui::TreeList boneList;

void Update(const wi::Canvas& canvas, float dt) override;
Expand Down
16 changes: 8 additions & 8 deletions WickedEngine/wiPhysics_Bullet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -680,13 +680,13 @@ namespace wi::physics

float mass = scale;
float capsule_height = scale;
float capsule_radius = scale;
float capsule_radius = scale * humanoid.ragdoll_fatness;

if (c == BODYPART_HEAD)
{
// Head doesn't necessarily have a child, so make up something reasonable:
capsule_height = 0.05f * scale;
capsule_radius = 0.1f * scale;
capsule_radius = 0.1f * scale * humanoid.ragdoll_headsize;
}
else
{
Expand All @@ -698,29 +698,29 @@ namespace wi::physics
switch (c)
{
case BODYPART_PELVIS:
capsule_radius = 0.1f * scale;
capsule_radius = 0.1f * scale * humanoid.ragdoll_fatness;
break;
case BODYPART_SPINE:
capsule_radius = 0.1f * scale;
capsule_radius = 0.1f * scale * humanoid.ragdoll_fatness;
capsule_height -= capsule_radius * 2;
break;
case BODYPART_LEFT_LOWER_ARM:
case BODYPART_RIGHT_LOWER_ARM:
capsule_radius = capsule_height * 0.15f;
capsule_radius = capsule_height * 0.15f * humanoid.ragdoll_fatness;
capsule_height += capsule_radius;
break;
case BODYPART_LEFT_UPPER_LEG:
case BODYPART_RIGHT_UPPER_LEG:
capsule_radius = capsule_height * 0.15f;
capsule_radius = capsule_height * 0.15f * humanoid.ragdoll_fatness;
capsule_height -= capsule_radius * 2;
break;
case BODYPART_LEFT_LOWER_LEG:
case BODYPART_RIGHT_LOWER_LEG:
capsule_radius = capsule_height * 0.15f;
capsule_radius = capsule_height * 0.15f * humanoid.ragdoll_fatness;
capsule_height -= capsule_radius;
break;
default:
capsule_radius = capsule_height * 0.2f;
capsule_radius = capsule_height * 0.2f * humanoid.ragdoll_fatness;
capsule_height -= capsule_radius * 2;
break;
}
Expand Down
166 changes: 166 additions & 0 deletions WickedEngine/wiScene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5181,6 +5181,172 @@ namespace wi::scene

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;
Expand Down
15 changes: 12 additions & 3 deletions WickedEngine/wiScene.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ namespace wi::scene
wi::ecs::ComponentManager<ColliderComponent>& colliders = componentLibrary.Register<ColliderComponent>("wi::scene::Scene::colliders", 2); // version = 2
wi::ecs::ComponentManager<ScriptComponent>& scripts = componentLibrary.Register<ScriptComponent>("wi::scene::Scene::scripts");
wi::ecs::ComponentManager<ExpressionComponent>& expressions = componentLibrary.Register<ExpressionComponent>("wi::scene::Scene::expressions");
wi::ecs::ComponentManager<HumanoidComponent>& humanoids = componentLibrary.Register<HumanoidComponent>("wi::scene::Scene::humanoids");
wi::ecs::ComponentManager<HumanoidComponent>& humanoids = componentLibrary.Register<HumanoidComponent>("wi::scene::Scene::humanoids", 1); // version = 1
wi::ecs::ComponentManager<wi::terrain::Terrain>& terrains = componentLibrary.Register<wi::terrain::Terrain>("wi::scene::Scene::terrains", 3); // version = 3
wi::ecs::ComponentManager<wi::Sprite>& sprites = componentLibrary.Register<wi::Sprite>("wi::scene::Scene::sprites");
wi::ecs::ComponentManager<wi::SpriteFont>& fonts = componentLibrary.Register<wi::SpriteFont>("wi::scene::Scene::fonts");
Expand Down Expand Up @@ -449,12 +449,21 @@ namespace wi::scene
return entity == other.entity;
}
};
// Given a ray, finds the closest intersection point against all mesh instances
// Given a ray, finds the closest intersection point against all mesh instances or collliders
// ray : the incoming ray that will be traced
// renderTypeMask : filter based on render type
// filterMask : filter based on type
// layerMask : filter based on layer
// lod : specify min level of detail for meshes
RayIntersectionResult Intersects(const wi::primitive::Ray& ray, uint32_t filterMask = wi::enums::FILTER_OPAQUE, uint32_t layerMask = ~0, uint32_t lod = 0) const;

// Given a ray, finds the first intersection point against all mesh instances or colliders
// returns true immediately if intersection was found, false otherwise
// ray : the incoming ray that will be traced
// filterMask : filter based on type
// layerMask : filter based on layer
// lod : specify min level of detail for meshes
bool IntersectsFirst(const wi::primitive::Ray& ray, uint32_t filterMask = wi::enums::FILTER_OPAQUE, uint32_t layerMask = ~0, uint32_t lod = 0) const;

struct SphereIntersectionResult
{
wi::ecs::Entity entity = wi::ecs::INVALID_ENTITY;
Expand Down
Loading

0 comments on commit 305ca85

Please sign in to comment.