Avoiding seam in 3D cylinder model - wpf

I have this texture representing internal area of a pipe:
I have created a simple cylinder model (GeometryModel3D) by dissecting the above texture to a grid of points and than wrapping this to form a cylinder, then mapping the above texture to it (each square on a grid having two triangles):
The left and right edges of the texture meet at the above black seam line, which is unwanted.
I could not use single vertex point for smooth continuation there, because that point corresponds to both 0 and 360 degrees which is single angle, but two distinct points on edges of the 2D texture.
So I have used pairs of vertices on the same location but each corresponding to different point in the source texture.
Is there anything I can do to hide the seam line?
Model creation code:
private void SceneViewerWindowOnLoaded(object sender, RoutedEventArgs e)
{
var imageSource = new BitmapImage(new Uri("pack://application:,,,/Resources/out1.jpg"));
var meshGeometry3D = new MeshGeometry3D();
double circumference = imageSource.PixelWidth;
double radius = (circumference / (2.0 * Math.PI));
double angleSegment = (2.0 * Math.PI / SegmentCountTrans);
double sizeSegmentLong = ((double)imageSource.PixelHeight / SegmentCountLong);
double sizeSegmentTrans = ((double)imageSource.PixelWidth / SegmentCountTrans);
for (int indexSegmentLong = 0; indexSegmentLong < SegmentCountLong; indexSegmentLong++)
{
double depth = (indexSegmentLong * sizeSegmentLong);
for (int indexSegmentTrans = 0; indexSegmentTrans < SegmentCountTrans; indexSegmentTrans++)
{
meshGeometry3D.Positions.Add(GetVertexPosition(indexSegmentTrans, depth, radius, angleSegment));
meshGeometry3D.TextureCoordinates.Add(new Point(indexSegmentTrans * sizeSegmentTrans, depth));
}
// add one extra vertex representing 360 degrees for complete wrap-around
meshGeometry3D.Positions.Add(GetVertexPosition(SegmentCountTrans, depth, radius, angleSegment));
meshGeometry3D.TextureCoordinates.Add(new Point(SegmentCountTrans * sizeSegmentTrans, depth));
}
meshGeometry3D.TriangleIndices.Clear();
for (int indexSegmentLong = 0; indexSegmentLong < (SegmentCountLong - 1); indexSegmentLong++)
{
for (int indexSegmentTrans = 0; indexSegmentTrans < SegmentCountTrans; indexSegmentTrans++)
{
int indexCurrent = (indexSegmentLong * (SegmentCountTrans + 1) + indexSegmentTrans);
meshGeometry3D.TriangleIndices.Add(indexCurrent);
meshGeometry3D.TriangleIndices.Add(indexCurrent + 1);
meshGeometry3D.TriangleIndices.Add(indexCurrent + SegmentCountTrans + 1);
meshGeometry3D.TriangleIndices.Add(indexCurrent + SegmentCountTrans + 1);
meshGeometry3D.TriangleIndices.Add(indexCurrent + 1);
meshGeometry3D.TriangleIndices.Add(indexCurrent + SegmentCountTrans + 2);
}
}
var geometryModel3D = new GeometryModel3D
{
Geometry = meshGeometry3D,
Material = new DiffuseMaterial
{
Brush = new ImageBrush(imageSource)
}
};
this.SceneViewer.Model = geometryModel3D;
}
private static Point3D GetVertexPosition(int indexSegmentAngle, double depth, double radius, double angleSegment)
{
double angle = (StartAngle + indexSegmentAngle * angleSegment);
return new Point3D(
radius * Math.Cos(angle),
radius * Math.Sin(angle),
-depth);
}
XAML code for the Viewport3D element containing the model:
<Viewport3D x:Name="Viewport3D">
<Viewport3D.Camera>
<PerspectiveCamera x:Name="Camera" Position="0.0,0.0,0.0" LookDirection="0.0,0.0,-1.0" UpDirection="0.0,1.0,0.0" FieldOfView="90"/>
</Viewport3D.Camera>
<ModelVisual3D>
<ModelVisual3D.Content>
<AmbientLight Color="White"/>
</ModelVisual3D.Content>
</ModelVisual3D>
<ModelVisual3D x:Name="ModelVisualModel"/>
</Viewport3D>

When you create your image brush try setting the TileMode to TileMode.Tile and ViewportUnits to BrushMappingMode.Absolute. If that still doesn't work then post a follow-up comment here and I'll try to reproduce it on my end.

I used DrawingBrush instead of Imagebrush. This fixed the seams and I also get an fps boost when setting the cachinghint.
Brush brush = new DrawingBrush(new ImageDrawing(_texture, new Rect(0,0,1,1)));
RenderOptions.SetCachingHint(brush , CachingHint.Cache);
RenderOptions.SetCacheInvalidationThresholdMinimum(brush , 0.5);
RenderOptions.SetCacheInvalidationThresholdMaximum(brush , 2.0);

Related

How can I zoom in on a certain region of a BitmapSource in a WPF Image control?

Let's say I have a 1280x1024 BitmapSource in a WPF Image control. There is a 100x100 "active" area of this image that I want to be able to zoom in on with a button click. I want to zoom as much as possible while maintaining the aspect ratio and keeping all "active" pixels visible. This is what I have:
XAML:
<DockPanel>
<Button DockPanel.Dock="Bottom" Content="Zoom" Click="Button_Click" />
<Border DockPanel.Dock="Top" ClipToBounds="True">
<Image Name="TheImage" />
</Border>
</DockPanel>
Code:
private const int WIDTH = 1280;
private const int HEIGHT = 1024;
private const int MIN_X = 100;
private const int MAX_X = 200;
private const int MIN_Y = 100;
private const int MAX_Y = 200;
public MainWindow()
{
InitializeComponent();
byte[] image = new byte[WIDTH * HEIGHT];
for (int y = MIN_Y; y <= MAX_Y; y++)
for (int x = MIN_X; x <= MAX_X; x++)
image[y * WIDTH + x] = byte.MaxValue;
TheImage.Source = BitmapSource.Create(WIDTH, HEIGHT, 96.0, 96.0, PixelFormats.Gray8, null, image, WIDTH);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
double factor = 1.0 / Math.Max((MAX_X - MIN_X) / (double)WIDTH, (MAX_Y - MIN_Y) / (double)HEIGHT);
Matrix matrix = Matrix.Identity;
matrix.ScaleAt(factor, factor, (MIN_X + MAX_X) / 2.0 / WIDTH * TheImage.ActualWidth, (MIN_Y + MAX_Y) / 2.0 / HEIGHT * TheImage.ActualHeight);
TheImage.RenderTransform = new MatrixTransform(matrix);
}
Here is what it looks like before zoom:
And here is after zoom:
It looks like the amount of zoom is correct, but I think the issue is that the center of the scaling should shift due to the scaling, but I'm not sure how to account for that upfront.
Figured it out. The simplest approach seems to be to first do a translation such that the center of the active area is in the center of the image, then do the scaling centered on that:
private void Button_Click(object sender, RoutedEventArgs e)
{
double factor = 1.0 / Math.Max((MAX_X - MIN_X) / (double)WIDTH, (MAX_Y - MIN_Y) / (double)HEIGHT);
Matrix matrix = Matrix.Identity;
matrix.Translate(0.5 * TheImage.ActualWidth - (MIN_X + MAX_X) / 2.0 / WIDTH * TheImage.ActualWidth, 0.5 * TheImage.ActualHeight - (MIN_Y + MAX_Y) / 2.0 / HEIGHT * TheImage.ActualHeight);
matrix.ScaleAt(factor, factor, 0.5 * TheImage.ActualWidth, 0.5 * TheImage.ActualHeight);
TheImage.RenderTransform = new MatrixTransform(matrix);
}
Screenshot after zoom:

How can I draw an Arc using MeshGeometry3D in WPF c#?

I want to draw Arc in Vieport3D using MeshGeometry3D.I have searched for it a lot but not found anything ,what I found is using PathGeometry.I am new to WPF so don't know much about 3D graphics.How can I do this?
thanks
here is the code which I wrote to create mesh-geometry for Arc.I hope this will help someone.
private GeometryModel3D GetModel(double radius, Vector3D normal, Point3D center, int resolution, double StartAngle, double EndAngle)
{
var mod = new GeometryModel3D();
var geo = new MeshGeometry3D();
// Generate the circle in the XZ-plane
// Add the center first
geo.Positions.Add(new Point3D(0, 0, 0));
// Iterate from angle 0 to 2*PI
double dev = (2 * Math.PI) / resolution;
double thik = 0.02;
//float spaceangle = StartAngle + 1;
if (StartAngle != EndAngle)
{
for (double i = StartAngle; i < EndAngle; i += dev)
{
geo.Positions.Add(new Point3D(radius * Math.Cos(i), 0, -radius * Math.Sin(i)));
geo.Positions.Add(new Point3D((radius-thik) * Math.Cos(i), 0, (-(radius-thik)) * Math.Sin(i)));
}
for (int i = 3; i < geo.Positions.Count; i += 1)
{
geo.TriangleIndices.Add(i - 3);
geo.TriangleIndices.Add(i - 1);
geo.TriangleIndices.Add(i - 2);
geo.TriangleIndices.Add(i - 1);
geo.TriangleIndices.Add(i);
geo.TriangleIndices.Add(i - 2);
}
}
mod.Geometry = geo;
// Create transforms
var trn = new Transform3DGroup();
// Up Vector (normal for XZ-plane)
var up = new Vector3D(0, 1, 0);
// Set normal length to 1
normal.Normalize();
var axis = Vector3D.CrossProduct(up, normal); // Cross product is rotation axis
var angle = Vector3D.AngleBetween(up, normal); // Angle to rotate
trn.Children.Add(new RotateTransform3D(new AxisAngleRotation3D(axis, angle)));
trn.Children.Add(new TranslateTransform3D(new Vector3D(center.X, center.Y, center.Z)));
mod.Transform = trn;
return mod;
}

How to draw a zigzag line?

I am creating a document based application and i want to draw a horizontal line underlying the text. But, line should not be straight. i want to draw a line like this.
Currently i am using System.Graphics object to draw any object.
private void DrawLine(Graphics g, Point Location, int iWidth)
{
iWidth = Convert.ToInt16(iWidth / 2);
iWidth = iWidth * 2;
Point[] pArray = new Point[Convert.ToInt16(iWidth / 2)];
int iNag = 2;
for (int i = 0; i < iWidth; i+=2)
{
pArray[(i / 2)] = new Point(Location.X + i , Location.Y + iNag);
if (iNag == 0)
iNag = 2;
else
iNag = 0;
}
g.DrawLines(Pens.Black, pArray);
}
UPDATED:
Above code is working fine and line draws perfectly but, this code effects on application performance. Is there another way to do this thing.
If you want fast drawing just make a png image of the line you want, with width larger than you need and then draw the image:
private void DrawLine(Graphics g, Point Location, int iWidth)
{
Rectangle srcRect = new Rectangle(0, 0, iWidth, zigzagLine.Height);
Rectangle dstRect = new Rectangle(Location.X, Location.Y, iWidth, zigzagLine.Height);
g.DrawImage(zigzagLine, dstRect, srcRect, GraphicsUnit.Pixel);
}
zigzagLine is the bitmap.
valter

WinForms control to setup clickable areas in a UserControl

Need to divide a UserControl with a background picture into multiple small clickable areas. Clicking them should simply raise an event, allowing to determine which particular area of the picture was clicked.
The obvious solution is using transparent labels. However, they are heavily flickering. So it looks like labels are not designed for this purpose, they take too much time to load.
So I'm thinking if any lighter option exists? To logically "slice up" the surface.
I also need a border around the areas, though.
on the user control do:
MouseClick += new System.Windows.Forms.MouseEventHandler(this.UserControl1_MouseClick);
and now in the UserControl1_MouseClick event do:
private void UserControl1_MouseClick(object sender, MouseEventArgs e)
{
int x = e.X;
int y = e.Y;
}
now let's divide the user control to a 10x10 area:
int xIdx = x / (Width / 10);
int yIdx = y / (Height / 10);
ClickOnArea(xIdx, yIdx);
in ClickOnArea method you just need to decide what to do in each area. maybe using a 2d array of Action
as for the border do this:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = e.Graphics;
Pen p = new Pen(Color.Black);
float xIdx = (float)(Width / 10.0);
float yIdx = (float)(Height / 10.0);
for (int i = 0; i < 10; i++)
{
float currVal = yIdx*i;
g.DrawLine(p, 0, currVal, Width, currVal);
}
g.DrawLine(p, 0, Height - 1, Width, Height - 1);
for (int j = 0; j < 10; j++)
{
float currVal = xIdx * j;
g.DrawLine(p, currVal, 0, currVal, Height);
}
g.DrawLine(p, Width - 1, 0, Width - 1, Height);
}

How to create a circle in 3D with known center point, radius and it is on a plane which perpendicular to a line in WPF?

How to create a circle in 3D with known center point, radius and it is on a plane which perpendicular to a line (vector) in WPF?
Following is an example based on the comment I posted earlier.
First we define a function to generate the model of the circle:
/// <summary>
/// Generates a model of a Circle given specified parameters
/// </summary>
/// <param name="radius">Radius of circle</param>
/// <param name="normal">Vector normal to circle's plane</param>
/// <param name="center">Center position of the circle</param>
/// <param name="resolution">Number of slices to iterate the circumference of the circle</param>
/// <returns>A GeometryModel3D representation of the circle</returns>
private GeometryModel3D GetCircleModel(double radius, Vector3D normal, Point3D center, int resolution)
{
var mod = new GeometryModel3D();
var geo = new MeshGeometry3D();
// Generate the circle in the XZ-plane
// Add the center first
geo.Positions.Add(new Point3D(0, 0, 0));
// Iterate from angle 0 to 2*PI
double t = 2 * Math.PI / resolution;
for (int i = 0; i < resolution; i++)
{
geo.Positions.Add(new Point3D(radius * Math.Cos(t * i), 0, -radius * Math.Sin(t * i)));
}
// Add points to MeshGeoemtry3D
for (int i = 0; i < resolution; i++)
{
var a = 0;
var b = i + 1;
var c = (i < (resolution - 1)) ? i + 2 : 1;
geo.TriangleIndices.Add(a);
geo.TriangleIndices.Add(b);
geo.TriangleIndices.Add(c);
}
mod.Geometry = geo;
// Create transforms
var trn = new Transform3DGroup();
// Up Vector (normal for XZ-plane)
var up = new Vector3D(0, 1, 0);
// Set normal length to 1
normal.Normalize();
var axis = Vector3D.CrossProduct(up, normal); // Cross product is rotation axis
var angle = Vector3D.AngleBetween(up, normal); // Angle to rotate
trn.Children.Add(new RotateTransform3D(new AxisAngleRotation3D(axis, angle)));
trn.Children.Add(new TranslateTransform3D(new Vector3D(center.X, center.Y, center.Z)));
mod.Transform = trn;
return mod;
}
Setting up our ViewPort3D:
<Grid Background="Black">
<Viewport3D Name="mainViewPort">
<Viewport3D.Camera>
<PerspectiveCamera NearPlaneDistance="0.1" FarPlaneDistance="100" UpDirection="0,1,0" Position="0,0,10"
LookDirection="0,0,-1" FieldOfView="60" />
</Viewport3D.Camera>
<ModelVisual3D>
<ModelVisual3D.Content>
<Model3DGroup>
<AmbientLight Color="#40FFFFFF" />
<DirectionalLight Color="#FFFFFF" Direction="1,-1,-1" />
</Model3DGroup>
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D>
</Grid>
Then to test:
private void AddCircleModel()
{
var mod = GetCircleModel(1.5, new Vector3D(0, 1, 1), new Point3D(0, -1, 0), 20);
mod.Material = new DiffuseMaterial(Brushes.Silver);
var vis = new ModelVisual3D() { Content = mod };
mainViewPort.Children.Add(vis);
}
Load up your window, call AddCircleModel(); and enjoy. Adjust view/parameters to your heart's content.
old thread but still, if anyone comes accross.
The "newb" Answer would work but is quitte lazy math and time consuming wize, here's why :
there are 2 for loops where really 1 is needed.
using transform after building a circle on (x,z) plane centered on (0,0,0) is overkill since you know the maths and vectors constraints.
I would suggest you implement it that way (i made it so it works for ellipses too, just set uSize = vSize for circles, u and v really just need to no be parallel for it to work but for mesh quality purposes i force them to be perpendicular):
public static Mesh CreateEllipse3D(Vector3 center, Vector3 normal, Vector3 u,
Vector3 v, float uSize, float vSize, int
subdiv = 30)
{
if (Math.Abs(Vector3.Dot(u, v)) != 0)
{
Debug.LogError("Vectors u and v must be orthogonals");
return new Mesh();
}
var mesh = new Mesh();
var vertices = new List<Vector3>();
var normals = new List<Vector3>();
var triangles = new List<int>();
vertices.Add(center);
float t = 2 * Mathf.PI / subdiv;
int a = 0, b, c;
for (int i = 0; i < subdiv; i++)
{
vertices.Add(center + u.normalized * uSize * Mathf.Cos(t * i) -
v.normalized * vSize * Mathf.Sin(t * i));
b = i + 1;
c = (i < (subdiv - 1)) ? i + 2 : 1;
triangles.Add(a);
triangles.Add(b);
triangles.Add(c);
normals.Add(normal);
normals.Add(normal);
normals.Add(normal);
}
mesh.vertices = vertices.ToArray();
mesh.triangles = triangles.ToArray();
mesh.normals = normals.ToArray();
return mesh;
}

Resources