Fix temporal coherence in SAT algorithm between two convex polyhedra
This commit is contained in:
parent
673e487f14
commit
6a22b3a81d
|
@ -489,6 +489,24 @@ bool SATAlgorithm::testCollisionConvexPolyhedronVsConvexPolyhedron(NarrowPhaseIn
|
||||||
minFaceIndex = lastFrameInfo.satMinAxisFaceIndex;
|
minFaceIndex = lastFrameInfo.satMinAxisFaceIndex;
|
||||||
isMinPenetrationFaceNormal = true;
|
isMinPenetrationFaceNormal = true;
|
||||||
isMinPenetrationFaceNormalPolyhedron1 = true;
|
isMinPenetrationFaceNormalPolyhedron1 = true;
|
||||||
|
|
||||||
|
// Compute the contact points between two faces of two convex polyhedra.
|
||||||
|
// If contact points have been found, we report them without running the whole SAT algorithm
|
||||||
|
if(computePolyhedronVsPolyhedronFaceContactPoints(isMinPenetrationFaceNormalPolyhedron1, polyhedron1, polyhedron2,
|
||||||
|
polyhedron1ToPolyhedron2, polyhedron2ToPolyhedron1, minFaceIndex,
|
||||||
|
narrowPhaseInfo, minPenetrationDepth)) {
|
||||||
|
|
||||||
|
lastFrameInfo.satIsAxisFacePolyhedron1 = isMinPenetrationFaceNormalPolyhedron1;
|
||||||
|
lastFrameInfo.satIsAxisFacePolyhedron2 = !isMinPenetrationFaceNormalPolyhedron1;
|
||||||
|
lastFrameInfo.satMinAxisFaceIndex = minFaceIndex;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else { // Contact points have not been found (the set of clipped points was empty)
|
||||||
|
|
||||||
|
// Therefore, we need to run the whole SAT algorithm again
|
||||||
|
isTemporalCoherenceValid = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (lastFrameInfo.satIsAxisFacePolyhedron2) { // If the previous separating axis (or axis with minimum penetration depth)
|
else if (lastFrameInfo.satIsAxisFacePolyhedron2) { // If the previous separating axis (or axis with minimum penetration depth)
|
||||||
|
@ -513,6 +531,24 @@ bool SATAlgorithm::testCollisionConvexPolyhedronVsConvexPolyhedron(NarrowPhaseIn
|
||||||
minFaceIndex = lastFrameInfo.satMinAxisFaceIndex;
|
minFaceIndex = lastFrameInfo.satMinAxisFaceIndex;
|
||||||
isMinPenetrationFaceNormal = true;
|
isMinPenetrationFaceNormal = true;
|
||||||
isMinPenetrationFaceNormalPolyhedron1 = false;
|
isMinPenetrationFaceNormalPolyhedron1 = false;
|
||||||
|
|
||||||
|
// Compute the contact points between two faces of two convex polyhedra.
|
||||||
|
// If contact points have been found, we report them without running the whole SAT algorithm
|
||||||
|
if(computePolyhedronVsPolyhedronFaceContactPoints(isMinPenetrationFaceNormalPolyhedron1, polyhedron1, polyhedron2,
|
||||||
|
polyhedron1ToPolyhedron2, polyhedron2ToPolyhedron1, minFaceIndex,
|
||||||
|
narrowPhaseInfo, minPenetrationDepth)) {
|
||||||
|
|
||||||
|
lastFrameInfo.satIsAxisFacePolyhedron1 = isMinPenetrationFaceNormalPolyhedron1;
|
||||||
|
lastFrameInfo.satIsAxisFacePolyhedron2 = !isMinPenetrationFaceNormalPolyhedron1;
|
||||||
|
lastFrameInfo.satMinAxisFaceIndex = minFaceIndex;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else { // Contact points have not been found (the set of clipped points was empty)
|
||||||
|
|
||||||
|
// Therefore, we need to run the whole SAT algorithm again
|
||||||
|
isTemporalCoherenceValid = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else { // If the previous separating axis (or axis with minimum penetration depth) was the cross product of two edges
|
else { // If the previous separating axis (or axis with minimum penetration depth) was the cross product of two edges
|
||||||
|
@ -544,6 +580,12 @@ bool SATAlgorithm::testCollisionConvexPolyhedronVsConvexPolyhedron(NarrowPhaseIn
|
||||||
// we will skip the entire SAT algorithm because the minimum separating axis did not change
|
// we will skip the entire SAT algorithm because the minimum separating axis did not change
|
||||||
isTemporalCoherenceValid = lastFrameInfo.wasColliding;
|
isTemporalCoherenceValid = lastFrameInfo.wasColliding;
|
||||||
|
|
||||||
|
// Temporal coherence is valid only if the two edges build a minkowski
|
||||||
|
// face (and the cross product is therefore a candidate for separating axis
|
||||||
|
if (isTemporalCoherenceValid && !testEdgesBuildMinkowskiFace(polyhedron1, edge1, polyhedron2, edge2, polyhedron1ToPolyhedron2)) {
|
||||||
|
isTemporalCoherenceValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (isTemporalCoherenceValid) {
|
if (isTemporalCoherenceValid) {
|
||||||
|
|
||||||
minPenetrationDepth = penetrationDepth;
|
minPenetrationDepth = penetrationDepth;
|
||||||
|
@ -672,102 +714,11 @@ bool SATAlgorithm::testCollisionConvexPolyhedronVsConvexPolyhedron(NarrowPhaseIn
|
||||||
|
|
||||||
if (reportContacts) {
|
if (reportContacts) {
|
||||||
|
|
||||||
const ConvexPolyhedronShape* referencePolyhedron = isMinPenetrationFaceNormalPolyhedron1 ? polyhedron1 : polyhedron2;
|
// Compute the contact points between two faces of two convex polyhedra.
|
||||||
const ConvexPolyhedronShape* incidentPolyhedron = isMinPenetrationFaceNormalPolyhedron1 ? polyhedron2 : polyhedron1;
|
bool contactsFound = computePolyhedronVsPolyhedronFaceContactPoints(isMinPenetrationFaceNormalPolyhedron1, polyhedron1,
|
||||||
const Transform& referenceToIncidentTransform = isMinPenetrationFaceNormalPolyhedron1 ? polyhedron1ToPolyhedron2 : polyhedron2ToPolyhedron1;
|
polyhedron2, polyhedron1ToPolyhedron2, polyhedron2ToPolyhedron1,
|
||||||
const Transform& incidentToReferenceTransform = isMinPenetrationFaceNormalPolyhedron1 ? polyhedron2ToPolyhedron1 : polyhedron1ToPolyhedron2;
|
minFaceIndex, narrowPhaseInfo, minPenetrationDepth);
|
||||||
|
assert(contactsFound);
|
||||||
assert(minPenetrationDepth > decimal(0.0));
|
|
||||||
|
|
||||||
const Vector3 axisReferenceSpace = referencePolyhedron->getFaceNormal(minFaceIndex);
|
|
||||||
const Vector3 axisIncidentSpace = referenceToIncidentTransform.getOrientation() * axisReferenceSpace;
|
|
||||||
|
|
||||||
// Compute the world normal
|
|
||||||
Vector3 normalWorld = isMinPenetrationFaceNormalPolyhedron1 ? narrowPhaseInfo->shape1ToWorldTransform.getOrientation() * axisReferenceSpace :
|
|
||||||
-(narrowPhaseInfo->shape2ToWorldTransform.getOrientation() * axisReferenceSpace);
|
|
||||||
|
|
||||||
// Get the reference face
|
|
||||||
HalfEdgeStructure::Face referenceFace = referencePolyhedron->getFace(minFaceIndex);
|
|
||||||
|
|
||||||
// Find the incident face on the other polyhedron (most anti-parallel face)
|
|
||||||
uint incidentFaceIndex = findMostAntiParallelFaceOnPolyhedron(incidentPolyhedron, axisIncidentSpace);
|
|
||||||
|
|
||||||
// Get the incident face
|
|
||||||
HalfEdgeStructure::Face incidentFace = incidentPolyhedron->getFace(incidentFaceIndex);
|
|
||||||
|
|
||||||
std::vector<Vector3> polygonVertices; // Vertices to clip of the incident face
|
|
||||||
std::vector<Vector3> planesNormals; // Normals of the clipping planes
|
|
||||||
std::vector<Vector3> planesPoints; // Points on the clipping planes
|
|
||||||
|
|
||||||
// Get all the vertices of the incident face (in the reference local-space)
|
|
||||||
std::vector<uint>::const_iterator it;
|
|
||||||
for (it = incidentFace.faceVertices.begin(); it != incidentFace.faceVertices.end(); ++it) {
|
|
||||||
const Vector3 faceVertexIncidentSpace = incidentPolyhedron->getVertexPosition(*it);
|
|
||||||
polygonVertices.push_back(incidentToReferenceTransform * faceVertexIncidentSpace);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the reference face clipping planes
|
|
||||||
uint currentEdgeIndex = referenceFace.edgeIndex;
|
|
||||||
uint firstEdgeIndex = currentEdgeIndex;
|
|
||||||
do {
|
|
||||||
|
|
||||||
// Get the adjacent edge
|
|
||||||
HalfEdgeStructure::Edge edge = referencePolyhedron->getHalfEdge(currentEdgeIndex);
|
|
||||||
|
|
||||||
// Get the twin edge
|
|
||||||
HalfEdgeStructure::Edge twinEdge = referencePolyhedron->getHalfEdge(edge.twinEdgeIndex);
|
|
||||||
|
|
||||||
// Compute the edge vertices and edge direction
|
|
||||||
Vector3 edgeV1 = referencePolyhedron->getVertexPosition(edge.vertexIndex);
|
|
||||||
Vector3 edgeV2 = referencePolyhedron->getVertexPosition(twinEdge.vertexIndex);
|
|
||||||
Vector3 edgeDirection = edgeV2 - edgeV1;
|
|
||||||
|
|
||||||
// Compute the normal of the clipping plane for this edge
|
|
||||||
// The clipping plane is perpendicular to the edge direction and the reference face normal
|
|
||||||
Vector3 clipPlaneNormal = axisReferenceSpace.cross(edgeDirection);
|
|
||||||
|
|
||||||
planesNormals.push_back(clipPlaneNormal);
|
|
||||||
planesPoints.push_back(edgeV1);
|
|
||||||
|
|
||||||
// Go to the next adjacent edge of the reference face
|
|
||||||
currentEdgeIndex = edge.nextEdgeIndex;
|
|
||||||
|
|
||||||
} while (currentEdgeIndex != firstEdgeIndex);
|
|
||||||
|
|
||||||
assert(planesNormals.size() > 0);
|
|
||||||
assert(planesNormals.size() == planesPoints.size());
|
|
||||||
|
|
||||||
// Clip the reference faces with the adjacent planes of the reference face
|
|
||||||
std::vector<Vector3> clipPolygonVertices = clipPolygonWithPlanes(polygonVertices, planesPoints, planesNormals);
|
|
||||||
assert(clipPolygonVertices.size() > 0);
|
|
||||||
|
|
||||||
// We only keep the clipped points that are below the reference face
|
|
||||||
const Vector3 referenceFaceVertex = referencePolyhedron->getVertexPosition(referencePolyhedron->getHalfEdge(firstEdgeIndex).vertexIndex);
|
|
||||||
std::vector<Vector3>::const_iterator itPoints;
|
|
||||||
for (itPoints = clipPolygonVertices.begin(); itPoints != clipPolygonVertices.end(); ++itPoints) {
|
|
||||||
|
|
||||||
// If the clip point is bellow the reference face
|
|
||||||
if (((*itPoints) - referenceFaceVertex).dot(axisReferenceSpace) < decimal(0.0)) {
|
|
||||||
|
|
||||||
// Convert the clip incident polyhedron vertex into the incident polyhedron local-space
|
|
||||||
Vector3 contactPointIncidentPolyhedron = referenceToIncidentTransform * (*itPoints);
|
|
||||||
|
|
||||||
// Project the contact point onto the reference face
|
|
||||||
Vector3 contactPointReferencePolyhedron = projectPointOntoPlane(*itPoints, axisReferenceSpace, referenceFaceVertex);
|
|
||||||
|
|
||||||
// Compute smooth triangle mesh contact if one of the two collision shapes is a triangle
|
|
||||||
TriangleShape::computeSmoothTriangleMeshContact(narrowPhaseInfo->collisionShape1, narrowPhaseInfo->collisionShape2,
|
|
||||||
isMinPenetrationFaceNormalPolyhedron1 ? contactPointReferencePolyhedron : contactPointIncidentPolyhedron,
|
|
||||||
isMinPenetrationFaceNormalPolyhedron1 ? contactPointIncidentPolyhedron : contactPointReferencePolyhedron,
|
|
||||||
narrowPhaseInfo->shape1ToWorldTransform, narrowPhaseInfo->shape2ToWorldTransform,
|
|
||||||
minPenetrationDepth, normalWorld);
|
|
||||||
|
|
||||||
// Create a new contact point
|
|
||||||
narrowPhaseInfo->addContactPoint(normalWorld, minPenetrationDepth,
|
|
||||||
isMinPenetrationFaceNormalPolyhedron1 ? contactPointReferencePolyhedron : contactPointIncidentPolyhedron,
|
|
||||||
isMinPenetrationFaceNormalPolyhedron1 ? contactPointIncidentPolyhedron : contactPointReferencePolyhedron);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lastFrameInfo.satIsAxisFacePolyhedron1 = isMinPenetrationFaceNormalPolyhedron1;
|
lastFrameInfo.satIsAxisFacePolyhedron1 = isMinPenetrationFaceNormalPolyhedron1;
|
||||||
|
@ -809,6 +760,116 @@ bool SATAlgorithm::testCollisionConvexPolyhedronVsConvexPolyhedron(NarrowPhaseIn
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compute the contact points between two faces of two convex polyhedra.
|
||||||
|
/// The method returns true if contact points have been found
|
||||||
|
bool SATAlgorithm::computePolyhedronVsPolyhedronFaceContactPoints(bool isMinPenetrationFaceNormalPolyhedron1,
|
||||||
|
const ConvexPolyhedronShape* polyhedron1, const ConvexPolyhedronShape* polyhedron2,
|
||||||
|
const Transform& polyhedron1ToPolyhedron2, const Transform& polyhedron2ToPolyhedron1,
|
||||||
|
uint minFaceIndex, NarrowPhaseInfo* narrowPhaseInfo, decimal minPenetrationDepth) const {
|
||||||
|
|
||||||
|
const ConvexPolyhedronShape* referencePolyhedron = isMinPenetrationFaceNormalPolyhedron1 ? polyhedron1 : polyhedron2;
|
||||||
|
const ConvexPolyhedronShape* incidentPolyhedron = isMinPenetrationFaceNormalPolyhedron1 ? polyhedron2 : polyhedron1;
|
||||||
|
const Transform& referenceToIncidentTransform = isMinPenetrationFaceNormalPolyhedron1 ? polyhedron1ToPolyhedron2 : polyhedron2ToPolyhedron1;
|
||||||
|
const Transform& incidentToReferenceTransform = isMinPenetrationFaceNormalPolyhedron1 ? polyhedron2ToPolyhedron1 : polyhedron1ToPolyhedron2;
|
||||||
|
|
||||||
|
assert(minPenetrationDepth > decimal(0.0));
|
||||||
|
|
||||||
|
const Vector3 axisReferenceSpace = referencePolyhedron->getFaceNormal(minFaceIndex);
|
||||||
|
const Vector3 axisIncidentSpace = referenceToIncidentTransform.getOrientation() * axisReferenceSpace;
|
||||||
|
|
||||||
|
// Compute the world normal
|
||||||
|
Vector3 normalWorld = isMinPenetrationFaceNormalPolyhedron1 ? narrowPhaseInfo->shape1ToWorldTransform.getOrientation() * axisReferenceSpace :
|
||||||
|
-(narrowPhaseInfo->shape2ToWorldTransform.getOrientation() * axisReferenceSpace);
|
||||||
|
|
||||||
|
// Get the reference face
|
||||||
|
HalfEdgeStructure::Face referenceFace = referencePolyhedron->getFace(minFaceIndex);
|
||||||
|
|
||||||
|
// Find the incident face on the other polyhedron (most anti-parallel face)
|
||||||
|
uint incidentFaceIndex = findMostAntiParallelFaceOnPolyhedron(incidentPolyhedron, axisIncidentSpace);
|
||||||
|
|
||||||
|
// Get the incident face
|
||||||
|
HalfEdgeStructure::Face incidentFace = incidentPolyhedron->getFace(incidentFaceIndex);
|
||||||
|
|
||||||
|
std::vector<Vector3> polygonVertices; // Vertices to clip of the incident face
|
||||||
|
std::vector<Vector3> planesNormals; // Normals of the clipping planes
|
||||||
|
std::vector<Vector3> planesPoints; // Points on the clipping planes
|
||||||
|
|
||||||
|
// Get all the vertices of the incident face (in the reference local-space)
|
||||||
|
std::vector<uint>::const_iterator it;
|
||||||
|
for (it = incidentFace.faceVertices.begin(); it != incidentFace.faceVertices.end(); ++it) {
|
||||||
|
const Vector3 faceVertexIncidentSpace = incidentPolyhedron->getVertexPosition(*it);
|
||||||
|
polygonVertices.push_back(incidentToReferenceTransform * faceVertexIncidentSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the reference face clipping planes
|
||||||
|
uint currentEdgeIndex = referenceFace.edgeIndex;
|
||||||
|
uint firstEdgeIndex = currentEdgeIndex;
|
||||||
|
do {
|
||||||
|
|
||||||
|
// Get the adjacent edge
|
||||||
|
HalfEdgeStructure::Edge edge = referencePolyhedron->getHalfEdge(currentEdgeIndex);
|
||||||
|
|
||||||
|
// Get the twin edge
|
||||||
|
HalfEdgeStructure::Edge twinEdge = referencePolyhedron->getHalfEdge(edge.twinEdgeIndex);
|
||||||
|
|
||||||
|
// Compute the edge vertices and edge direction
|
||||||
|
Vector3 edgeV1 = referencePolyhedron->getVertexPosition(edge.vertexIndex);
|
||||||
|
Vector3 edgeV2 = referencePolyhedron->getVertexPosition(twinEdge.vertexIndex);
|
||||||
|
Vector3 edgeDirection = edgeV2 - edgeV1;
|
||||||
|
|
||||||
|
// Compute the normal of the clipping plane for this edge
|
||||||
|
// The clipping plane is perpendicular to the edge direction and the reference face normal
|
||||||
|
Vector3 clipPlaneNormal = axisReferenceSpace.cross(edgeDirection);
|
||||||
|
|
||||||
|
planesNormals.push_back(clipPlaneNormal);
|
||||||
|
planesPoints.push_back(edgeV1);
|
||||||
|
|
||||||
|
// Go to the next adjacent edge of the reference face
|
||||||
|
currentEdgeIndex = edge.nextEdgeIndex;
|
||||||
|
|
||||||
|
} while (currentEdgeIndex != firstEdgeIndex);
|
||||||
|
|
||||||
|
assert(planesNormals.size() > 0);
|
||||||
|
assert(planesNormals.size() == planesPoints.size());
|
||||||
|
|
||||||
|
// Clip the reference faces with the adjacent planes of the reference face
|
||||||
|
std::vector<Vector3> clipPolygonVertices = clipPolygonWithPlanes(polygonVertices, planesPoints, planesNormals);
|
||||||
|
assert(clipPolygonVertices.size() > 0);
|
||||||
|
|
||||||
|
// We only keep the clipped points that are below the reference face
|
||||||
|
const Vector3 referenceFaceVertex = referencePolyhedron->getVertexPosition(referencePolyhedron->getHalfEdge(firstEdgeIndex).vertexIndex);
|
||||||
|
std::vector<Vector3>::const_iterator itPoints;
|
||||||
|
bool contactPointsFound = false;
|
||||||
|
for (itPoints = clipPolygonVertices.begin(); itPoints != clipPolygonVertices.end(); ++itPoints) {
|
||||||
|
|
||||||
|
// If the clip point is bellow the reference face
|
||||||
|
if (((*itPoints) - referenceFaceVertex).dot(axisReferenceSpace) < decimal(0.0)) {
|
||||||
|
|
||||||
|
contactPointsFound = true;
|
||||||
|
|
||||||
|
// Convert the clip incident polyhedron vertex into the incident polyhedron local-space
|
||||||
|
Vector3 contactPointIncidentPolyhedron = referenceToIncidentTransform * (*itPoints);
|
||||||
|
|
||||||
|
// Project the contact point onto the reference face
|
||||||
|
Vector3 contactPointReferencePolyhedron = projectPointOntoPlane(*itPoints, axisReferenceSpace, referenceFaceVertex);
|
||||||
|
|
||||||
|
// Compute smooth triangle mesh contact if one of the two collision shapes is a triangle
|
||||||
|
TriangleShape::computeSmoothTriangleMeshContact(narrowPhaseInfo->collisionShape1, narrowPhaseInfo->collisionShape2,
|
||||||
|
isMinPenetrationFaceNormalPolyhedron1 ? contactPointReferencePolyhedron : contactPointIncidentPolyhedron,
|
||||||
|
isMinPenetrationFaceNormalPolyhedron1 ? contactPointIncidentPolyhedron : contactPointReferencePolyhedron,
|
||||||
|
narrowPhaseInfo->shape1ToWorldTransform, narrowPhaseInfo->shape2ToWorldTransform,
|
||||||
|
minPenetrationDepth, normalWorld);
|
||||||
|
|
||||||
|
// Create a new contact point
|
||||||
|
narrowPhaseInfo->addContactPoint(normalWorld, minPenetrationDepth,
|
||||||
|
isMinPenetrationFaceNormalPolyhedron1 ? contactPointReferencePolyhedron : contactPointIncidentPolyhedron,
|
||||||
|
isMinPenetrationFaceNormalPolyhedron1 ? contactPointIncidentPolyhedron : contactPointReferencePolyhedron);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return contactPointsFound;
|
||||||
|
}
|
||||||
|
|
||||||
// Find and return the index of the polyhedron face with the most anti-parallel face normal given a direction vector
|
// Find and return the index of the polyhedron face with the most anti-parallel face normal given a direction vector
|
||||||
// This is used to find the incident face on a polyhedron of a given reference face of another polyhedron
|
// This is used to find the incident face on a polyhedron of a given reference face of another polyhedron
|
||||||
uint SATAlgorithm::findMostAntiParallelFaceOnPolyhedron(const ConvexPolyhedronShape* polyhedron, const Vector3& direction) const {
|
uint SATAlgorithm::findMostAntiParallelFaceOnPolyhedron(const ConvexPolyhedronShape* polyhedron, const Vector3& direction) const {
|
||||||
|
|
|
@ -96,6 +96,13 @@ class SATAlgorithm {
|
||||||
const Vector3& edgeDirectionCapsuleSpace,
|
const Vector3& edgeDirectionCapsuleSpace,
|
||||||
const Transform& polyhedronToCapsuleTransform, Vector3& outAxis) const;
|
const Transform& polyhedronToCapsuleTransform, Vector3& outAxis) const;
|
||||||
|
|
||||||
|
/// Compute the contact points between two faces of two convex polyhedra.
|
||||||
|
bool computePolyhedronVsPolyhedronFaceContactPoints(bool isMinPenetrationFaceNormalPolyhedron1, const ConvexPolyhedronShape* polyhedron1,
|
||||||
|
const ConvexPolyhedronShape* polyhedron2, const Transform& polyhedron1ToPolyhedron2,
|
||||||
|
const Transform& polyhedron2ToPolyhedron1, uint minFaceIndex,
|
||||||
|
NarrowPhaseInfo* narrowPhaseInfo, decimal minPenetrationDepth) const;
|
||||||
|
|
||||||
|
|
||||||
public :
|
public :
|
||||||
|
|
||||||
// -------------------- Methods -------------------- //
|
// -------------------- Methods -------------------- //
|
||||||
|
|
Loading…
Reference in New Issue
Block a user