git-svn-id: https://triangle.svn.codeplex.com/svn@78017 0e2699bc-83d4-4a8f-98e7-55e24ab8c7a5
This commit is contained in:
@@ -592,11 +592,11 @@ namespace MeshExplorer
|
||||
UpdateLog();
|
||||
}
|
||||
|
||||
private void CreateVoronoi()
|
||||
private bool CreateVoronoi()
|
||||
{
|
||||
if (mesh == null)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mesh.IsPolygon)
|
||||
@@ -616,7 +616,7 @@ namespace MeshExplorer
|
||||
DarkMessageBox.Show("Exception - Bounded Voronoi", ex.Message, MessageBoxButtons.OK);
|
||||
}
|
||||
|
||||
this.voronoi = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -624,12 +624,11 @@ namespace MeshExplorer
|
||||
this.voronoi = new StandardVoronoi(mesh);
|
||||
}
|
||||
|
||||
if (this.voronoi != null)
|
||||
{
|
||||
// HACK: List<Vertex> -> ICollection<Point> ? Nope, no way.
|
||||
// Vertex[] -> ICollection<Point> ? Well, ok.
|
||||
renderManager.Set(voronoi.Vertices.ToArray(), voronoi.Edges, false);
|
||||
}
|
||||
// HACK: List<Vertex> -> ICollection<Point> ? Nope, no way.
|
||||
// Vertex[] -> ICollection<Point> ? Well, ok.
|
||||
renderManager.Set(voronoi.Vertices.ToArray(), voronoi.Edges, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ShowLog()
|
||||
@@ -722,8 +721,7 @@ namespace MeshExplorer
|
||||
{
|
||||
if (this.voronoi == null)
|
||||
{
|
||||
CreateVoronoi();
|
||||
menuViewVoronoi.Checked = true;
|
||||
menuViewVoronoi.Checked = CreateVoronoi();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -167,12 +167,13 @@ namespace TriangleNet.Rendering
|
||||
|
||||
// Change or add as many colors as you like...
|
||||
private static Color[] regionColors = {
|
||||
Color.FromArgb(200, 0, 0, 255),
|
||||
Color.Transparent,
|
||||
Color.FromArgb(200, 0, 255, 0),
|
||||
Color.FromArgb(200, 255, 0, 0),
|
||||
Color.FromArgb(200, 0, 0, 255),
|
||||
Color.FromArgb(200, 0, 255, 255),
|
||||
Color.FromArgb(200, 255, 255, 0),
|
||||
Color.FromArgb(200, 255, 0, 255),
|
||||
Color.FromArgb(200, 0, 255, 0),
|
||||
Color.FromArgb(200, 127, 0, 255),
|
||||
Color.FromArgb(200, 0, 127, 255)
|
||||
};
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
|
||||
namespace TriangleNet.Rendering.GDI
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
|
||||
public class ImageRenderer
|
||||
{
|
||||
ColorManager colors = ImageRenderer.LightScheme();
|
||||
|
||||
public ColorManager ColorScheme
|
||||
{
|
||||
get { return colors; }
|
||||
set { colors = value; }
|
||||
}
|
||||
|
||||
public bool EnableRegions { get; set; }
|
||||
|
||||
public bool EnablePoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Export the mesh to PNG format.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The current mesh.</param>
|
||||
/// <param name="width">The desired width (pixel) of the image.</param>
|
||||
/// <param name="file">The PNG filename.</param>
|
||||
/// <param name="regions">Enable rendering of regions.</param>
|
||||
/// <param name="points">Enable rendering of points.</param>
|
||||
public static void Save(Mesh mesh, string file = null, int width = 800,
|
||||
bool regions = false, bool points = true)
|
||||
{
|
||||
// Check file name
|
||||
if (String.IsNullOrWhiteSpace(file))
|
||||
{
|
||||
file = String.Format("mesh-{0}.png", DateTime.Now.ToString("yyyy-M-d-hh-mm-ss"));
|
||||
}
|
||||
|
||||
// Ensure .png extension.
|
||||
if (file.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Path.ChangeExtension(file, ".png");
|
||||
}
|
||||
|
||||
var renderer = new ImageRenderer();
|
||||
|
||||
renderer.EnableRegions = regions;
|
||||
renderer.EnablePoints = points;
|
||||
|
||||
var bitmap = renderer.Render(mesh, width);
|
||||
|
||||
bitmap.Save(file, ImageFormat.Png);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Export the mesh to PNG format.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The current mesh.</param>
|
||||
/// <param name="width">The desired width (pixel) of the image.</param>
|
||||
/// <returns>The bitmap.</returns>
|
||||
/// <remarks>
|
||||
/// The width has to be at least 2 * sqrt(n), n the number of vertices.
|
||||
/// Otherwise, an empty bitmap
|
||||
/// </remarks>
|
||||
public Bitmap Render(Mesh mesh, int width = 800)
|
||||
{
|
||||
Bitmap bitmap;
|
||||
|
||||
// Check if the specified width is reasonable
|
||||
if (width < 2 * Math.Sqrt(mesh.Vertices.Count))
|
||||
{
|
||||
return new Bitmap(1, 1);
|
||||
}
|
||||
|
||||
var bounds = mesh.Bounds;
|
||||
|
||||
// World margin on each side
|
||||
float margin = (float)bounds.Height * 0.05f;
|
||||
float scale = width / ((float)bounds.Width + 2 * margin);
|
||||
|
||||
var target = new Rectangle(0, 0, width, (int)((bounds.Height + 2 * margin) * scale));
|
||||
|
||||
bitmap = new Bitmap(width, target.Height, PixelFormat.Format32bppPArgb);
|
||||
|
||||
using (var g = Graphics.FromImage(bitmap))
|
||||
{
|
||||
g.Clear(colors.Background);
|
||||
g.SmoothingMode = SmoothingMode.HighQuality;
|
||||
|
||||
var context = new RenderContext(new Projection(target), colors);
|
||||
context.Add(mesh, true);
|
||||
|
||||
if (EnableRegions)
|
||||
{
|
||||
context.Add(GetRegions(mesh));
|
||||
}
|
||||
|
||||
if (!EnablePoints)
|
||||
{
|
||||
context.Enable(3, false);
|
||||
}
|
||||
|
||||
var renderer = new LayerRenderer();
|
||||
renderer.Context = context;
|
||||
renderer.RenderTarget = g;
|
||||
renderer.Render();
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private int[] GetRegions(Mesh mesh)
|
||||
{
|
||||
mesh.Renumber();
|
||||
|
||||
var labels = new int[mesh.Triangles.Count];
|
||||
var regions = new HashSet<int>();
|
||||
|
||||
foreach (var t in mesh.Triangles)
|
||||
{
|
||||
labels[t.ID] = t.Label;
|
||||
regions.Add(t.Label);
|
||||
}
|
||||
|
||||
if (colors.ColorDictionary == null)
|
||||
{
|
||||
colors.CreateColorDictionary(regions.Count);
|
||||
}
|
||||
|
||||
return labels;
|
||||
}
|
||||
|
||||
public static ColorManager LightScheme()
|
||||
{
|
||||
var colors = new ColorManager();
|
||||
|
||||
colors.Background = Color.White;
|
||||
colors.Point = new SolidBrush(Color.FromArgb(60, 80, 120));
|
||||
colors.SteinerPoint = new SolidBrush(Color.DarkGreen);
|
||||
colors.Line = new Pen(Color.FromArgb(200, 200, 200));
|
||||
colors.Segment = new Pen(Color.SteelBlue);
|
||||
colors.VoronoiLine = new Pen(Color.FromArgb(160, 170, 180));
|
||||
|
||||
return colors;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,7 @@
|
||||
<Compile Include="ColorManager.cs" />
|
||||
<Compile Include="ExtensionMethods.cs" />
|
||||
<Compile Include="GDI\FunctionRenderer.cs" />
|
||||
<Compile Include="GDI\ImageRenderer.cs" />
|
||||
<Compile Include="GDI\LayerRenderer.cs" />
|
||||
<Compile Include="GDI\MeshRenderer.cs" />
|
||||
<Compile Include="GDI\Native\GradientFillMode.cs" />
|
||||
|
||||
@@ -76,8 +76,8 @@ namespace TriangleNet.Geometry
|
||||
/// <summary>
|
||||
/// Try to find a point inside the contour.
|
||||
/// </summary>
|
||||
/// <param name="limit">The number of iterations on each segment (default = 8).</param>
|
||||
/// <param name="eps">Threshold for co-linear points (default = 2e-6).</param>
|
||||
/// <param name="limit">The number of iterations on each segment (default = 5).</param>
|
||||
/// <param name="eps">Threshold for co-linear points (default = 2e-5).</param>
|
||||
/// <returns>Point inside the contour</returns>
|
||||
/// <exception cref="Exception">Throws if no point could be found.</exception>
|
||||
/// <remarks>
|
||||
@@ -86,16 +86,16 @@ namespace TriangleNet.Geometry
|
||||
/// on the bisecting line, or, if <see cref="IPredicates.CounterClockwise"/> is less than
|
||||
/// eps, on the perpendicular line.
|
||||
/// A given number of points will be tested (limit), while the distance to the contour
|
||||
/// boundary will be reduced in each iteration (with a factor of 1/2^i, i = 1 ... limit).
|
||||
/// boundary will be reduced in each iteration (with a factor 1 / 2^i, i = 1 ... limit).
|
||||
/// </remarks>
|
||||
public Point FindInteriorPoint(int limit = 8, double eps = 2e-6)
|
||||
public Point FindInteriorPoint(int limit = 5, double eps = 2e-5)
|
||||
{
|
||||
var point = new Point(0.0, 0.0);
|
||||
|
||||
if (convex)
|
||||
{
|
||||
int count = this.Points.Count;
|
||||
|
||||
var point = new Point(0.0, 0.0);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
point.x += this.Points[i].x;
|
||||
@@ -152,8 +152,8 @@ namespace TriangleNet.Geometry
|
||||
c = contour[(i + 2) % length];
|
||||
|
||||
// Corner point.
|
||||
bx = b.X;
|
||||
by = b.Y;
|
||||
bx = b.x;
|
||||
by = b.y;
|
||||
|
||||
// NOTE: if we knew the contour points were in counterclockwise order, we
|
||||
// could skip concave corners and search only in one direction.
|
||||
@@ -163,14 +163,14 @@ namespace TriangleNet.Geometry
|
||||
if (Math.Abs(h) < eps)
|
||||
{
|
||||
// Points are nearly co-linear. Use perpendicular direction.
|
||||
dx = (c.Y - a.Y) / 2;
|
||||
dy = (a.X - c.X) / 2;
|
||||
dx = (c.y - a.y) / 2;
|
||||
dy = (a.x - c.x) / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Direction [midpoint(a-c) -> corner point]
|
||||
dx = (a.X + c.X) / 2 - bx;
|
||||
dy = (a.Y + c.Y) / 2 - by;
|
||||
dx = (a.x + c.x) / 2 - bx;
|
||||
dy = (a.y + c.y) / 2 - by;
|
||||
}
|
||||
|
||||
// Move around the contour.
|
||||
@@ -182,8 +182,8 @@ namespace TriangleNet.Geometry
|
||||
for (int j = 0; j < limit; j++)
|
||||
{
|
||||
// Search in direction.
|
||||
test.X = bx + dx / h;
|
||||
test.Y = by + dy / h;
|
||||
test.x = bx + dx * h;
|
||||
test.y = by + dy * h;
|
||||
|
||||
if (bounds.Contains(test) && IsPointInPolygon(test, contour))
|
||||
{
|
||||
@@ -191,15 +191,15 @@ namespace TriangleNet.Geometry
|
||||
}
|
||||
|
||||
// Search in opposite direction (see NOTE above).
|
||||
test.X = bx - dx / h;
|
||||
test.Y = by - dy / h;
|
||||
test.x = bx - dx * h;
|
||||
test.y = by - dy * h;
|
||||
|
||||
if (bounds.Contains(test) && IsPointInPolygon(test, contour))
|
||||
{
|
||||
return test;
|
||||
}
|
||||
|
||||
h = 2.0 * h;
|
||||
h = h / 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace TriangleNet.Geometry
|
||||
#endif
|
||||
|
||||
public Point()
|
||||
: this(0, 0, 0)
|
||||
: this(0.0, 0.0, 0)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,30 @@ namespace TriangleNet.Geometry
|
||||
|
||||
int label;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the segments boundary mark.
|
||||
/// </summary>
|
||||
public int Label
|
||||
{
|
||||
get { return label; }
|
||||
set { label = value; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the first endpoints index.
|
||||
/// </summary>
|
||||
public int P0
|
||||
{
|
||||
get { return v0.id; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the second endpoints index.
|
||||
/// </summary>
|
||||
public int P1
|
||||
{
|
||||
get { return v1.id; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Segment" /> class.
|
||||
/// </summary>
|
||||
@@ -64,30 +88,5 @@ namespace TriangleNet.Geometry
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first endpoints index.
|
||||
/// </summary>
|
||||
public int P0
|
||||
{
|
||||
get { return v0.id; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the second endpoints index.
|
||||
/// </summary>
|
||||
public int P1
|
||||
{
|
||||
get { return v1.id; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the segments boundary mark.
|
||||
/// </summary>
|
||||
public int Label
|
||||
{
|
||||
get { return label; }
|
||||
set { label = value; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace TriangleNet.IO
|
||||
{
|
||||
public bool IsSupported(string file)
|
||||
{
|
||||
string ext = Path.GetExtension(file);
|
||||
string ext = Path.GetExtension(file).ToLower();
|
||||
|
||||
if (ext == ".node" || ext == ".poly" || ext == ".ele")
|
||||
{
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
|
||||
namespace TriangleNet.Meshing.Iterators
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using TriangleNet.Geometry;
|
||||
using TriangleNet.Topology;
|
||||
|
||||
public class VertexCirculator
|
||||
{
|
||||
List<Otri> cache = new List<Otri>();
|
||||
|
||||
Mesh mesh;
|
||||
|
||||
public VertexCirculator(Mesh mesh)
|
||||
{
|
||||
this.mesh = mesh;
|
||||
|
||||
mesh.MakeVertexMap();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerate all vertices adjacent to given vertex.
|
||||
/// </summary>
|
||||
/// <param name="vertex">The center vertex.</param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<Vertex> EnumerateVertices(Vertex vertex)
|
||||
{
|
||||
BuildCache(vertex, true);
|
||||
|
||||
foreach (var item in cache)
|
||||
{
|
||||
yield return item.Dest();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerate all triangles adjacent to given vertex.
|
||||
/// </summary>
|
||||
/// <param name="vertex">The center vertex.</param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<ITriangle> EnumerateTriangles(Vertex vertex)
|
||||
{
|
||||
BuildCache(vertex, false);
|
||||
|
||||
foreach (var item in cache)
|
||||
{
|
||||
yield return item.tri;
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildCache(Vertex vertex, bool vertices)
|
||||
{
|
||||
cache.Clear();
|
||||
|
||||
Otri init = vertex.tri;
|
||||
Otri next = default(Otri);
|
||||
Otri prev = default(Otri);
|
||||
|
||||
init.Copy(ref next);
|
||||
|
||||
// Move counter-clockwise around the vertex.
|
||||
while (next.tri.id != Mesh.DUMMY)
|
||||
{
|
||||
cache.Add(next);
|
||||
|
||||
next.Copy(ref prev);
|
||||
next.Onext();
|
||||
|
||||
if (next.Equals(init))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (next.tri.id == Mesh.DUMMY)
|
||||
{
|
||||
// We reached the boundary. To get all adjacent triangles, start
|
||||
// again at init triangle and now move clockwise.
|
||||
init.Copy(ref next);
|
||||
|
||||
if (vertices)
|
||||
{
|
||||
// Don't forget to add the vertex lying on the boundary.
|
||||
prev.Lnext();
|
||||
cache.Add(prev);
|
||||
}
|
||||
|
||||
next.Oprev();
|
||||
|
||||
while (next.tri.id != Mesh.DUMMY)
|
||||
{
|
||||
cache.Insert(0, next);
|
||||
|
||||
next.Oprev();
|
||||
|
||||
if (next.Equals(init))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,8 @@ namespace TriangleNet.Smoothing
|
||||
/// </remarks>
|
||||
public class SimpleSmoother : ISmoother
|
||||
{
|
||||
IPredicates predicates;
|
||||
TrianglePool pool;
|
||||
Configuration config;
|
||||
|
||||
IVoronoiFactory factory;
|
||||
|
||||
@@ -30,19 +31,34 @@ namespace TriangleNet.Smoothing
|
||||
/// Initializes a new instance of the <see cref="SimpleSmoother" /> class.
|
||||
/// </summary>
|
||||
public SimpleSmoother()
|
||||
: this(new VoronoiFactory(), RobustPredicates.Default)
|
||||
: this(new VoronoiFactory())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SimpleSmoother" /> class.
|
||||
/// </summary>
|
||||
/// <param name="factory">Voronoi object factory.</param>
|
||||
/// <param name="predicates">Geometric predicates implementation.</param>
|
||||
public SimpleSmoother(IVoronoiFactory factory, IPredicates predicates)
|
||||
public SimpleSmoother(IVoronoiFactory factory)
|
||||
{
|
||||
this.factory = factory;
|
||||
this.predicates = predicates;
|
||||
this.pool = new TrianglePool();
|
||||
|
||||
this.config = new Configuration(
|
||||
() => RobustPredicates.Default,
|
||||
() => pool.Restart());
|
||||
|
||||
this.options = new ConstraintOptions() { ConformingDelaunay = true };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SimpleSmoother" /> class.
|
||||
/// </summary>
|
||||
/// <param name="factory">Voronoi object factory.</param>
|
||||
/// <param name="config">Configuration.</param>
|
||||
public SimpleSmoother(IVoronoiFactory factory, Configuration config)
|
||||
{
|
||||
this.factory = factory;
|
||||
this.config = config;
|
||||
|
||||
this.options = new ConstraintOptions() { ConformingDelaunay = true };
|
||||
}
|
||||
@@ -56,18 +72,21 @@ namespace TriangleNet.Smoothing
|
||||
{
|
||||
var smoothedMesh = (Mesh)mesh;
|
||||
|
||||
var mesher = new GenericMesher(config);
|
||||
var predicates = config.Predicates();
|
||||
|
||||
// The smoother should respect the mesh segment splitting behavior.
|
||||
this.options.SegmentSplitting = smoothedMesh.behavior.NoBisect;
|
||||
|
||||
// Take a few smoothing rounds (Lloyd's algorithm).
|
||||
for (int i = 0; i < limit; i++)
|
||||
{
|
||||
Step(smoothedMesh, factory);
|
||||
Step(smoothedMesh, factory, predicates);
|
||||
|
||||
// Actually, we only want to rebuild, if mesh is no longer
|
||||
// Actually, we only want to rebuild, if the mesh is no longer
|
||||
// Delaunay. Flipping edges could be the right choice instead
|
||||
// of re-triangulating...
|
||||
smoothedMesh = (Mesh)Rebuild(smoothedMesh).Triangulate(options);
|
||||
smoothedMesh = (Mesh)mesher.Triangulate(Rebuild(smoothedMesh), options);
|
||||
|
||||
factory.Reset();
|
||||
}
|
||||
@@ -75,7 +94,7 @@ namespace TriangleNet.Smoothing
|
||||
smoothedMesh.CopyTo((Mesh)mesh);
|
||||
}
|
||||
|
||||
private void Step(Mesh mesh, IVoronoiFactory factory)
|
||||
private void Step(Mesh mesh, IVoronoiFactory factory, IPredicates predicates)
|
||||
{
|
||||
var voronoi = new BoundedVoronoi(mesh, factory, predicates);
|
||||
|
||||
|
||||
@@ -40,11 +40,10 @@ namespace TriangleNet.Tools
|
||||
dx = vertex.x - org.x;
|
||||
dy = vertex.y - org.y;
|
||||
|
||||
// To interpolate vertex attributes for the new vertex inserted at
|
||||
// the circumcenter, define a coordinate system with a xi-axis,
|
||||
// directed from the triangle's origin to its destination, and
|
||||
// an eta-axis, directed from its origin to its apex.
|
||||
// Calculate the xi and eta coordinates of the circumcenter.
|
||||
// To interpolate vertex attributes for the new vertex, define a
|
||||
// coordinate system with a xi-axis directed from the triangle's
|
||||
// origin to its destination, and an eta-axis, directed from its
|
||||
// origin to its apex.
|
||||
xi = (yao * dx - xao * dy) * (2.0 * denominator);
|
||||
eta = (xdo * dy - ydo * dx) * (2.0 * denominator);
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
<Compile Include="Meshing\IConstraintMesher.cs" />
|
||||
<Compile Include="Meshing\IMesh.cs" />
|
||||
<Compile Include="Meshing\IQualityMesher.cs" />
|
||||
<Compile Include="Meshing\Iterators\VertexCirculator.cs" />
|
||||
<Compile Include="Meshing\ITriangulator.cs" />
|
||||
<Compile Include="Meshing\QualityOptions.cs" />
|
||||
<Compile Include="Meshing\QualityMesher.cs" />
|
||||
|
||||
@@ -298,7 +298,7 @@ namespace TriangleNet
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
index = 0;
|
||||
index = offset = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user