Skip to content

Commit

Permalink
Fixed soft body collision vs scaled shapes (#655)
Browse files Browse the repository at this point in the history
* None of the shapes dealt properly with scaled shapes. Now the scale is passed separately just like the other collision functions and each shape handles the scale explicitly.
* Added option to shoot a soft body cube
* Fixed bugs in the handling of cylinder and convex hull shapes didn't return the proper normal when an edge was closest to the soft body vertex
  • Loading branch information
jrouwe authored Aug 5, 2023
1 parent 55195f2 commit ad3ca18
Show file tree
Hide file tree
Showing 33 changed files with 249 additions and 102 deletions.
4 changes: 2 additions & 2 deletions Jolt/Physics/Collision/Shape/BoxShape.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,10 @@ void BoxShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShape
ioCollector.AddHit({ TransformedShape::sGetBodyID(ioCollector.GetContext()), inSubShapeIDCreator.GetID() });
}

void BoxShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Array<SoftBodyVertex> &ioVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
void BoxShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
{
Mat44 inverse_transform = inCenterOfMassTransform.InversedRotationTranslation();
Vec3 half_extent = mHalfExtent;
Vec3 half_extent = inScale.Abs() * mHalfExtent;

for (SoftBodyVertex &v : ioVertices)
if (v.mInvMass > 0.0f)
Expand Down
2 changes: 1 addition & 1 deletion Jolt/Physics/Collision/Shape/BoxShape.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class JPH_EXPORT BoxShape final : public ConvexShape
virtual void CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override;

// See: Shape::ColideSoftBodyVertices
virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;

// See Shape::GetTrianglesStart
virtual void GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const override;
Expand Down
21 changes: 14 additions & 7 deletions Jolt/Physics/Collision/Shape/CapsuleShape.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -322,29 +322,36 @@ void CapsuleShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubS
ioCollector.AddHit({ TransformedShape::sGetBodyID(ioCollector.GetContext()), inSubShapeIDCreator.GetID() });
}

void CapsuleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Array<SoftBodyVertex> &ioVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
void CapsuleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
{
JPH_ASSERT(IsValidScale(inScale));

Mat44 inverse_transform = inCenterOfMassTransform.InversedRotationTranslation();

// Get scaled capsule
float scale = abs(inScale.GetX());
float half_height_of_cylinder = scale * mHalfHeightOfCylinder;
float radius = scale * mRadius;

for (SoftBodyVertex &v : ioVertices)
if (v.mInvMass > 0.0f)
{
// Calculate penetration
Vec3 local_pos = inverse_transform * v.mPosition;
if (abs(local_pos.GetY()) <= mHalfHeightOfCylinder)
if (abs(local_pos.GetY()) <= half_height_of_cylinder)
{
// Near cylinder
Vec3 normal = local_pos;
normal.SetY(0.0f);
float normal_length = normal.Length();
float penetration = mRadius - normal_length;
float penetration = radius - normal_length;
if (penetration > v.mLargestPenetration)
{
v.mLargestPenetration = penetration;

// Calculate contact point and normal
normal = normal_length > 0.0f? normal / normal_length : Vec3::sAxisX();
Vec3 point = mRadius * normal;
Vec3 point = radius * normal;

// Store collision
v.mCollisionPlane = Plane::sFromPointAndNormal(point, normal).GetTransformed(inCenterOfMassTransform);
Expand All @@ -354,17 +361,17 @@ void CapsuleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Arr
else
{
// Near cap
Vec3 center = Vec3(0, Sign(local_pos.GetY()) * mHalfHeightOfCylinder, 0);
Vec3 center = Vec3(0, Sign(local_pos.GetY()) * half_height_of_cylinder, 0);
Vec3 delta = local_pos - center;
float distance = delta.Length();
float penetration = mRadius - distance;
float penetration = radius - distance;
if (penetration > v.mLargestPenetration)
{
v.mLargestPenetration = penetration;

// Calculate contact point and normal
Vec3 normal = delta / distance;
Vec3 point = center + mRadius * normal;
Vec3 point = center + radius * normal;

// Store collision
v.mCollisionPlane = Plane::sFromPointAndNormal(point, normal).GetTransformed(inCenterOfMassTransform);
Expand Down
2 changes: 1 addition & 1 deletion Jolt/Physics/Collision/Shape/CapsuleShape.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class JPH_EXPORT CapsuleShape final : public ConvexShape
virtual void CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override;

// See: Shape::ColideSoftBodyVertices
virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;

// See Shape::TransformShape
virtual void TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCollector &ioCollector) const override;
Expand Down
4 changes: 2 additions & 2 deletions Jolt/Physics/Collision/Shape/CompoundShape.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -246,10 +246,10 @@ void CompoundShape::DrawGetSupportingFace(DebugRenderer *inRenderer, RMat44Arg i
}
#endif // JPH_DEBUG_RENDERER

void CompoundShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
void CompoundShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
{
for (const SubShape &shape : mSubShapes)
shape.mShape->CollideSoftBodyVertices(inCenterOfMassTransform * Mat44::sRotationTranslation(shape.GetRotation(), shape.GetPositionCOM()), ioVertices, inDeltaTime, inDisplacementDueToGravity, inCollidingShapeIndex);
shape.mShape->CollideSoftBodyVertices(inCenterOfMassTransform * Mat44::sRotationTranslation(shape.GetRotation(), shape.GetPositionCOM()), shape.TransformScale(inScale), ioVertices, inDeltaTime, inDisplacementDueToGravity, inCollidingShapeIndex);
}

void CompoundShape::TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCollector &ioCollector) const
Expand Down
2 changes: 1 addition & 1 deletion Jolt/Physics/Collision/Shape/CompoundShape.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class JPH_EXPORT CompoundShape : public Shape
#endif // JPH_DEBUG_RENDERER

// See: Shape::ColideSoftBodyVertices
virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;

// See Shape::TransformShape
virtual void TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCollector &ioCollector) const override;
Expand Down
88 changes: 62 additions & 26 deletions Jolt/Physics/Collision/Shape/ConvexHullShape.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1058,35 +1058,66 @@ void ConvexHullShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inS
ioCollector.AddHit({ TransformedShape::sGetBodyID(ioCollector.GetContext()), inSubShapeIDCreator.GetID() });
}

void ConvexHullShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Array<SoftBodyVertex> &ioVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
void ConvexHullShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
{
Mat44 inverse_transform = inCenterOfMassTransform.InversedRotationTranslation();

Vec3 inv_scale = inScale.Reciprocal();
bool is_not_scaled = ScaleHelpers::IsNotScaled(inScale);
float scale_flip = ScaleHelpers::IsInsideOut(inScale)? -1.0f : 1.0f;

for (SoftBodyVertex &v : ioVertices)
if (v.mInvMass > 0.0f)
{
Vec3 local_pos = inverse_transform * v.mPosition;

// Find most facing plane
float max_distance = -FLT_MAX;
const Plane *max_plane = nullptr;
for (const Plane &p : mPlanes)
Vec3 max_plane_normal = Vec3::sZero();
uint max_plane_idx = 0;
if (is_not_scaled)
{
float distance = p.SignedDistance(local_pos);
if (distance > max_distance)
// Without scale, it is trivial to calculate the distance to the hull
for (const Plane &p : mPlanes)
{
max_distance = distance;
max_plane = &p;
float distance = p.SignedDistance(local_pos);
if (distance > max_distance)
{
max_distance = distance;
max_plane_normal = p.GetNormal();
max_plane_idx = uint(&p - mPlanes.data());
}
}
}
else
{
// When there's scale we need to calculate the planes first
for (uint i = 0; i < (uint)mPlanes.size(); ++i)
{
// Calculate plane normal and point by scaling the original plane
Vec3 plane_normal = (inv_scale * mPlanes[i].GetNormal()).Normalized();
Vec3 plane_point = inScale * mPoints[mVertexIdx[mFaces[i].mFirstVertex]].mPosition;

float distance = plane_normal.Dot(local_pos - plane_point);
if (distance > max_distance)
{
max_distance = distance;
max_plane_normal = plane_normal;
max_plane_idx = i;
}
}
}
bool is_outside = max_distance > 0.0f;

// Project point onto that plane
Vec3 closest_point = local_pos - max_distance * max_plane_normal;

// Check edges if we're outside the hull (when inside we know the closest face is also the closest point to the surface)
float closest_edge_distance = FLT_MAX;
Plane closest_plane = *max_plane;
if (max_distance <= 0.0f)
if (is_outside)
{
// Loop over edges
const Face &face = mFaces[int(max_plane - mPlanes.data())];
float closest_point_dist_sq = FLT_MAX;
const Face &face = mFaces[max_plane_idx];
for (const uint8 *v_start = &mVertexIdx[face.mFirstVertex], *v1 = v_start, *v_end = v_start + face.mNumVertices; v1 < v_end; ++v1)
{
// Find second point
Expand All @@ -1095,36 +1126,41 @@ void ConvexHullShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform,
v2 = v_start;

// Get edge points
Vec3 p1 = mPoints[*v1].mPosition;
Vec3 p2 = mPoints[*v2].mPosition;
Vec3 p1 = inScale * mPoints[*v1].mPosition;
Vec3 p2 = inScale * mPoints[*v2].mPosition;

// Check if the position is outside the edge (if not, the face will be closer)
Vec3 edge_normal = (p2 - p1).Cross(max_plane->GetNormal());
if (edge_normal.Dot(local_pos - p1) > 0.0f)
Vec3 edge_normal = (p2 - p1).Cross(max_plane_normal);
if (scale_flip * edge_normal.Dot(local_pos - p1) > 0.0f)
{
// Get closest point on edge
uint32 set;
Vec3 closest = ClosestPoint::GetClosestPointOnLine(p1 - local_pos, p2 - local_pos, set);
float distance = closest.Length();
if (distance < closest_edge_distance)
{
closest_edge_distance = distance;
Vec3 point = local_pos + closest;
Vec3 normal = (local_pos - point).NormalizedOr(max_plane->GetNormal());
closest_plane = Plane::sFromPointAndNormal(point, normal);
}
float distance_sq = closest.LengthSq();
if (distance_sq < closest_point_dist_sq)
closest_point = local_pos + closest;
}
}
}

// Closest point on edge
float penetration = -(closest_edge_distance != FLT_MAX? closest_edge_distance : max_distance);
// Check if this is the largest penetration
Vec3 normal = local_pos - closest_point;
float normal_length = normal.Length();
float penetration = normal_length;
if (is_outside)
penetration = -penetration;
else
normal = -normal;
if (penetration > v.mLargestPenetration)
{
v.mLargestPenetration = penetration;

// Calculate contact plane
normal = normal_length > 0.0f? normal / normal_length : max_plane_normal;
Plane plane = Plane::sFromPointAndNormal(closest_point, normal);

// Store collision
v.mCollisionPlane = closest_plane.GetTransformed(inCenterOfMassTransform);
v.mCollisionPlane = plane.GetTransformed(inCenterOfMassTransform);
v.mCollidingShapeIndex = inCollidingShapeIndex;
}
}
Expand Down
2 changes: 1 addition & 1 deletion Jolt/Physics/Collision/Shape/ConvexHullShape.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ class JPH_EXPORT ConvexHullShape final : public ConvexShape
virtual void CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override;

// See: Shape::ColideSoftBodyVertices
virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;

// See Shape::GetTrianglesStart
virtual void GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const override;
Expand Down
21 changes: 14 additions & 7 deletions Jolt/Physics/Collision/Shape/CylinderShape.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,10 +300,17 @@ void CylinderShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSub
ioCollector.AddHit({ TransformedShape::sGetBodyID(ioCollector.GetContext()), inSubShapeIDCreator.GetID() });
}

void CylinderShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Array<SoftBodyVertex> &ioVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
void CylinderShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
{
JPH_ASSERT(IsValidScale(inScale));

Mat44 inverse_transform = inCenterOfMassTransform.InversedRotationTranslation();

// Get scaled cylinder
Vec3 abs_scale = inScale.Abs();
float half_height = abs_scale.GetY() * mHalfHeight;
float radius = abs_scale.GetX() * mRadius;

for (SoftBodyVertex &v : ioVertices)
if (v.mInvMass > 0.0f)
{
Expand All @@ -313,29 +320,29 @@ void CylinderShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Ar
Vec3 side_normal = local_pos;
side_normal.SetY(0.0f);
float side_normal_length = side_normal.Length();
float side_penetration = mRadius - side_normal_length;
float side_penetration = radius - side_normal_length;

// Calculate penetration into top or bottom plane
float top_penetration = mHalfHeight - abs(local_pos.GetY());
float top_penetration = half_height - abs(local_pos.GetY());

Vec3 point, normal;
if (side_penetration < 0.0f && top_penetration < 0.0f)
{
// We're outside the cylinder height and radius
point = side_normal * (mRadius / side_normal_length) + Vec3(0, mHalfHeight * Sign(local_pos.GetY()), 0);
normal = point.Normalized();
point = side_normal * (radius / side_normal_length) + Vec3(0, half_height * Sign(local_pos.GetY()), 0);
normal = (local_pos - point).NormalizedOr(Vec3::sAxisY());
}
else if (side_penetration < top_penetration)
{
// Side surface is closest
normal = side_normal_length > 0.0f? side_normal / side_normal_length : Vec3::sAxisX();
point = mRadius * normal;
point = radius * normal;
}
else
{
// Top or bottom plane is closest
normal = Vec3(0, Sign(local_pos.GetY()), 0);
point = mHalfHeight * normal;
point = half_height * normal;
}

// Calculate penetration
Expand Down
2 changes: 1 addition & 1 deletion Jolt/Physics/Collision/Shape/CylinderShape.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class JPH_EXPORT CylinderShape final : public ConvexShape
virtual void CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override;

// See: Shape::ColideSoftBodyVertices
virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;

// See Shape::TransformShape
virtual void TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCollector &ioCollector) const override;
Expand Down
4 changes: 2 additions & 2 deletions Jolt/Physics/Collision/Shape/HeightFieldShape.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1544,9 +1544,9 @@ void HeightFieldShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &in
// A height field doesn't have volume, so we can't test insideness
}

void HeightFieldShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
void HeightFieldShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
{
sCollideSoftBodyVerticesUsingRayCast(*this, inCenterOfMassTransform, ioVertices, inDeltaTime, inDisplacementDueToGravity, inCollidingShapeIndex);
sCollideSoftBodyVerticesUsingRayCast(*this, inCenterOfMassTransform, inScale, ioVertices, inDeltaTime, inDisplacementDueToGravity, inCollidingShapeIndex);
}

void HeightFieldShape::sCastConvexVsHeightField(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, const Shape *inShape, Vec3Arg inScale, [[maybe_unused]] const ShapeFilter &inShapeFilter, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, CastShapeCollector &ioCollector)
Expand Down
2 changes: 1 addition & 1 deletion Jolt/Physics/Collision/Shape/HeightFieldShape.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ class JPH_EXPORT HeightFieldShape final : public Shape
virtual void CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override;

// See: Shape::ColideSoftBodyVertices
virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;

// See Shape::GetTrianglesStart
virtual void GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const override;
Expand Down
Loading

0 comments on commit ad3ca18

Please sign in to comment.