Texture on 3D object in C# - c#

I'm using VS 2008 - C# Express. I want to reflect a text block onto 3D mesh object. I found a sample code snippet in a website. I added into my project and then ran it, unfortunately the debugger sent the error message "The type or namespace name "Run" could not be found...". What am I doing wrong ? Is there a missing namespace ?
Could you help me.
Regards.
The code snippet :
public static ModelVisual3D CreateTextLabel3D(string text, Brush textColor, bool bDoubleSided, double height, Point3D center, Vector3D over, Vector3D up)
{
// First we need a textblock containing the text of our label
TextBlock tb = new TextBlock(new Run(text));
tb.Foreground = textColor;
tb.FontFamily = new FontFamily("Arial");
// Now use that TextBlock as the brush for a material
DiffuseMaterial mat = new DiffuseMaterial();
mat.Brush = new VisualBrush(tb);
// We just assume the characters are square
double width = text.Length * height;
// Since the parameter coming in was the center of the label,
// we need to find the four corners
// p0 is the lower left corner
// p1 is the upper left
// p2 is the lower right
// p3 is the upper right
Point3D p0 = center - width / 2 * over - height / 2 * up;
Point3D p1 = p0 + up * 1 * height;
Point3D p2 = p0 + over * width;
Point3D p3 = p0 + up * 1 * height + over * width;
// Now build the geometry for the sign. It's just a
// rectangle made of two triangles, on each side.
MeshGeometry3D mg = new MeshGeometry3D();
mg.Positions = new Point3DCollection();
mg.Positions.Add(p0); // 0
mg.Positions.Add(p1); // 1
mg.Positions.Add(p2); // 2
mg.Positions.Add(p3); // 3
if (bDoubleSided)
{
mg.Positions.Add(p0); // 4
mg.Positions.Add(p1); // 5
mg.Positions.Add(p2); // 6
mg.Positions.Add(p3); // 7
}
mg.TriangleIndices.Add(0);
mg.TriangleIndices.Add(3);
mg.TriangleIndices.Add(1);
mg.TriangleIndices.Add(0);
mg.TriangleIndices.Add(2);
mg.TriangleIndices.Add(3);
if (bDoubleSided)
{
mg.TriangleIndices.Add(4);
mg.TriangleIndices.Add(5);
mg.TriangleIndices.Add(7);
mg.TriangleIndices.Add(4);
mg.TriangleIndices.Add(7);
mg.TriangleIndices.Add(6);
}
// These texture coordinates basically stretch the
// TextBox brush to cover the full side of the label.
mg.TextureCoordinates.Add(new Point(0, 1));
mg.TextureCoordinates.Add(new Point(0, 0));
mg.TextureCoordinates.Add(new Point(1, 1));
mg.TextureCoordinates.Add(new Point(1, 0));
if (bDoubleSided)
{
mg.TextureCoordinates.Add(new Point(1, 1));
mg.TextureCoordinates.Add(new Point(1, 0));
mg.TextureCoordinates.Add(new Point(0, 1));
mg.TextureCoordinates.Add(new Point(0, 0));
}
// And that's all. Return the result.
ModelVisual3D mv3d = new ModelVisual3D();
mv3d.Content = new GeometryModel3D(mg, mat);;
return mv3d;
}

You need to make sure you've got:
using System.Windows.Documents;
in your code, which is where the Run class resides.
You might need to add a reference to PresentationFramework.dll as well.

Make sure to add, at the top of your file:
using System.Windows.Documents;
The Run class is System.Windows.Documents.Run - not part of System.Windows.

Related

How can I fill the circular sector of an elliptic shape with a color gradient?

What I want to do is to create this rotating cone visual effect.
I had previously used DirectX for that.
What i have tried so far:
Even if I'm changing the thickness to 50 or more, the Arc is still not filled.
public partial class Form1 : Form
{
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
var g = e.Graphics;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
var center = new Point(pictureBox1.Width / 2, pictureBox1.Height / 2);
var innerR = 30;
var thickness = 20;
var startAngle = 0;
var arcLength = 360;
var outerR = innerR + thickness;
var outerRect = new Rectangle
(center.X - outerR, center.Y - outerR, 2 * outerR, 2 * outerR);
var innerRect = new Rectangle
(center.X - innerR, center.Y - innerR, 2 * innerR, 2 * innerR);
using (var p = new GraphicsPath())
{
p.AddArc(outerRect, startAngle, arcLength);
p.AddArc(innerRect, startAngle + arcLength, -arcLength);
p.CloseFigure();
e.Graphics.FillPath(Brushes.Green, p);
e.Graphics.DrawPath(Pens.Green, p);
}
}
}
I want to be able to fill the arc even when the thickness is 20 or less.
Or when the value of the innerR radius changes.
The goal is to be able to fill the arc in any case.
Here's one method of drawing that cone.
It looks like a Radar sweep, so you may want to define the sweep angle and the rotation speed (how much the current rotation angle is increased based on the Timer's interval).
Using a standard System.Windows.Forms.Timer to invalidate the Canvas that contains the Image you're showing here.
The Radar contour (the external perimeter) is centered on the canvas and drawn in relation to the thickness specified (so it's always sized as the canvas bounds). It doesn't necessarily be a perfect circle, it can be elliptical (as in the image here)
The Cone section is drawn adding an Arc to a GraphicsPath and is closed drawing two lines, from the center point of the outer GraphicsPath to the starting and ending points of the Arc (I think this is a simple method to generate a curved conic figure, it can be used in different situations and lets you generate different shapes almost without calculations, see the code about this)
It's filled with a LinearGradientBrush, the section near the center has less transparency than the section near the border; adjust as required
Each time the rotation angle reaches 360°, it's reset to 0.
This is delegated to the Timer's Tick event handler
=> Built with .Net 7, but if you need to adapt it to .Net Framework, the only things to change are the syntax of the using blocks, remove the null-forgiving operator from here: canvas!.ClientRectangle and nullable reference types (e.g., change object? to just object)
public partial class SomeForm : Form {
public SomeForm() {
InitializeComponent();
radarTimer.Interval = 100;
radarTimer.Tick += RadarTimer_Tick;
}
float coneSweepAngle = 36.0f;
float coneRotationAngle = .0f;
float radarSpeed = 1.8f;
float radarThickness = 5.0f;
System.Windows.Forms.Timer radarTimer = new System.Windows.Forms.Timer();
private void RadarTimer_Tick(object? sender, EventArgs e) {
coneRotationAngle += radarSpeed;
coneRotationAngle %= 360.0f;
canvas.Invalidate();
}
private void canvas_Paint(object sender, PaintEventArgs e) {
var center = new PointF(canvas.Width / 2.0f, canvas.Height / 2.0f);
RectangleF outerRect = canvas!.ClientRectangle;
outerRect.Inflate(-(radarThickness / 2.0f), -(radarThickness / 2.0f));
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
using var pathOuter = new GraphicsPath();
using var pathInner = new GraphicsPath();
pathOuter.AddEllipse(outerRect);
pathInner.StartFigure();
pathInner.AddArc(outerRect, coneRotationAngle, coneSweepAngle);
var arcPoints = pathInner.PathPoints;
PointF first = arcPoints[0];
PointF last = arcPoints[arcPoints.Length - 1];
pathInner.AddLines(new[] { center, last, center, first });
pathInner.CloseFigure();
using var outerPen = new Pen(Color.FromArgb(100, Color.Red), radarThickness);
using var innerBrush = new LinearGradientBrush(
center, first, Color.FromArgb(200, Color.Orange), Color.FromArgb(20, Color.Orange));
e.Graphics.FillPath(innerBrush, pathInner);
e.Graphics.DrawPath(outerPen, pathOuter);
}
}
This is how it works:

c# draw the z axis

I'm drawing coordinate axes in picturebox
void draw_cor()
{
int w = pictureBox1.ClientSize.Width / 2;
int h = pictureBox1.ClientSize.Height / 2;
Refresh();
Graphics e = pictureBox1.CreateGraphics();
e.TranslateTransform(w, h);
DrawXAxis(new Point(-w, 0), new Point(w, 0), e);
DrawYAxis(new Point(0, h), new Point(0, -h), e);
DrawZAxis(new Point(-pictureBox1.ClientSize.Width , pictureBox1.ClientSize.Height), new Point(pictureBox1.ClientSize.Width, -pictureBox1.ClientSize.Height ), e);
}
markup and text for the x axis as an example
private void DrawXAxis(Point start, Point end, Graphics g)
{
for (int i = Step; i < end.X; i += Step)
{
g.DrawLine(Pens.Black, i, -5, i, 5);
DrawText(new Point(i, 5), (i / Step).ToString(), g, false);
}
for (int i = -Step; i > start.X; i -= Step)
{
g.DrawLine(Pens.Black, i, -5, i, 5);
DrawText(new Point(i, 5), (i / Step).ToString(), g, false);
}
g.DrawLine(Pens.Black, start, end);
g.DrawString("X", new Font(Font.FontFamily, 10, FontStyle.Bold), Brushes.Black, new Point(end.X - 15, end.Y));
}
private void DrawText(Point point, string text, Graphics g, bool isYAxis)
{
var f = new Font(Font.FontFamily, 6);
var size = g.MeasureString(text, f);
var pt = isYAxis
? new PointF(point.X + 1, point.Y - size.Height / 2)
: new PointF(point.X - size.Width / 2, point.Y + 1);
var rect = new RectangleF(pt, size);
g.DrawString(text, f, Brushes.Black, rect);
}
can someone explain how to make a method for marking the z axis?
I understand that the shift should be diagonal in both x and y, but nothing worked out for me and no markup appears on the screen.(so far I have managed to draw only a straight line diagonally )
upd:
private void DrawZAxis(Point start, Point end, Graphics g)
{
for (int i = -Step, j=Step ; i > start.X; i -= Step,j += Step)
{
g.DrawLine(Pens.Black, new Point(i-5, j), new Point(i+5, j));
DrawText(new Point(i, j), (i / -Step).ToString(), g, false);
}
...
}
I seem to have succeeded, but I ran into such a problem:
that is, the markup is not always on the coordinate axis. How to avoid this? It is necessary that the numbers are always on the axis (I suppose I should calculate the coefficient when the window is scaled, but only where to add it or by what to multiply?)
You are dealing with 3D data, so you should use 3D tools to transform your axes, and your data for that matter.
So you need to define a projection from 3D space to 2D space. This is usually done by defining a projection matrix. There are multiple projections to chose from, it looks like your projection is Oblique, but orthographic and perspective projections are also common. The System.Numerics.Vectors library has classes for Matrix4x4, vector2/3/4, with methods to create your projection and transform your vectors.
After transforming a vector you can simply keep the x/y values and discard the z-value to get your image coordinates. Note that if using a perspective transform you need a vector4 and divide the x/y/z elements by W.
Armed with these tools it should be a fairly simple thing to generate start/end points for each axis, and create tick-marks in 3D, before projecting everything to 2D for drawing.
Another option would be to just do everything in Wpf3D to start with, this will likely make some functionality like rotating the camera simpler.

Creating a single hexagon in C# using DrawPolygon

Ok so i have created a triangle but I cant for the life of me work out the coordinates to create a simple hexagon,
Point[] shape = new Point[3];
shape[0] = new Point(200, 100);
shape[1] = new Point(300, 200);
shape[2] = new Point(100, 200);
This makes a triangle but I cant figure out the x and y values for a hexagon, sounds like a simple question but my brain just isn't working correctly today, Below is the array for the hexagon I just can't figure out the values.
Point[] shape = new Point[6];
shape[0] = new Point(0, 0);
shape[1] = new Point(0, 0);
shape[2] = new Point(0, 0);
shape[3] = new Point(0, 0);
shape[4] = new Point(0, 0);
shape[5] = new Point(0, 0);
Any help would be great thanks!
Since I've already written a comment, I guess I should demonstrate that in some real code.
I created a WinForms application with a Panel object on which I can draw. Then I've overridden the Paint event on that to draw me a hexagon.
private void panel1_Paint(object sender, PaintEventArgs e)
{
var graphics = e.Graphics;
//Get the middle of the panel
var x_0 = panel1.Width / 2;
var y_0 = panel1.Height / 2;
var shape = new PointF[6];
var r = 70; //70 px radius
//Create 6 points
for(int a=0; a < 6; a++)
{
shape[a] = new PointF(
x_0 + r * (float)Math.Cos(a * 60 * Math.PI / 180f),
y_0 + r * (float)Math.Sin(a * 60 * Math.PI / 180f));
}
graphics.DrawPolygon(Pens.Red, shape);
}
This then draws
As I said, the key is to view the hexagon as a "discrete" circle. The points are all computed as being on the outer part of a perfect circle, which are then connected with a straight line. You can create all regular n-Point shapes with this technique (a pentagon e.g. as a 5-regular shape ;))
So, you just "inscribe" the 6 points in the circle to get your hexagon, as shown in this diagram with a regular 5-point shape:
Then remember that you can compute the (x,y) coordinates of a point given its polar coordinates (r, phi) as
To which you can also add an offset , which is in my case the center of the frame I'm drawing in.

Crop a diagonal area from an image in WPF

I want to crop from an image using user-drawn rectangles on a canvas. The rectangles can be moved, re-sized, and rotated.
When the user selects "Get Cropped Image", the area inside the rectangle should be saved in a second image location on the page, which I can do perfectly well, so long as the rectangle is not rotated. (Straight-forward use of CroppedBitmap.) However, when the rectangle is at an angle I do not know how to perform the crop.
This is what I want to do (forgive my poor MS Paint skills):
My questions are:
1) How do I correctly track or calculate the points of the rectangle?
and,
2) Once I have the points, how do I crop the rotated rectangle?
EDIT:
Thanks to user Rotem, I believe that I have the answer to the second question. Using code modified from the following answers: Answer 1, Answer 2, I am seeing good results. Unfortunately, I am still unable to track the correct location points for the rectangle, so I cannot fully test this as of yet.
public static Bitmap CropRotatedRect(Bitmap source, System.Drawing.Rectangle rect, float angle, bool HighQuality)
{
Bitmap result = new Bitmap((int)rect.Width, (int)rect.Height);
using (Graphics g = Graphics.FromImage(result))
{
g.InterpolationMode = HighQuality ? InterpolationMode.HighQualityBicubic : InterpolationMode.Default;
using (Matrix mat = new Matrix())
{
mat.Translate(-rect.Location.X, -rect.Location.Y);
mat.RotateAt(-(angle), rect.Location);
g.Transform = mat;
g.DrawImage(source, new System.Drawing.Point(0, 0));
}
}
return result;
}
EDIT:
The answer to the first point is much easier than I had originally thought. You can always get the top-left corner of the rectangle by calling—
double top = Canvas.GetTop(rect);
double left = Canvas.GetLeft(rect);
You can then calculate the rest of the points using the width and the height—
Point topLeft = new Point(left, top);
Point topRight = new Point(left + rect.Width, top);
Point bottomLeft = new Point(left, top + rect.Height);
Point bottomRight = new Point(left + rect.Width, top + rect.Height);
Point centerPoint = new Point(left + (rect.Width / 2), top + (rect.Height / 2));
If your rectangle is rotated, then you have to translate these points to determine where they truly lie on the canvas—
public Point TranslatePoint(Point center, Point p, double angle)
{
// get the point relative to (0, 0) by subtracting the center of the rotated shape.
Point relToOrig = new Point(p.X - center.X, p.Y - center.Y);
double angleInRadians = angle * Math.PI / 180;
double sinOfA = Math.Sin(angleInRadians);
double cosOfA = Math.Cos(angleInRadians);
Point translatedPoint = new Point(relToOrig.X * cosOfA - relToOrig.Y * sinOfA,
relToOrig.X * sinOfA + relToOrig.Y * cosOfA);
return new Point(translatedPoint.X + center.X, translatedPoint.Y + center.Y);
}
Once you are able to translate the top-left corner, you can use Rotem's cropping method. You can also calculate the position of the rest of the rectangle, so you are able to determine if the rectangle is within the bounds of the image, if it is touching an edge, or any other thing that you might want to do in regards to the position.
I discovered the answer to my own question(s), and made the appropriate edits along the way. Please see above for the answer.

How to draw a dashed line over an object?

I am drawing a line on a control on my Windows form like this:
// Get Graphics object from chart
Graphics graph = e.ChartGraphics.Graphics;
PointF point1 = PointF.Empty;
PointF point2 = PointF.Empty;
// Set Maximum and minimum points
point1.X = -110;
point1.Y = -110;
point2.X = 122;
point2.Y = 122;
// Convert relative coordinates to absolute coordinates.
point1 = e.ChartGraphics.GetAbsolutePoint(point1);
point2 = e.ChartGraphics.GetAbsolutePoint(point2);
// Draw connection line
graph.DrawLine(new Pen(Color.Yellow, 3), point1, point2);
I would like to know if it is possible to draw a dashed (dotted) line instead of a regular solid line?
It's pretty simple once you figure out the formatting that defines the dashes:
float[] dashValues = { 5, 2, 15, 4 };
Pen blackPen = new Pen(Color.Black, 5);
blackPen.DashPattern = dashValues;
e.Graphics.DrawLine(blackPen, new Point(5, 5), new Point(405, 5));
The numbers in the float array represent dash lengths of different colors. So for a simple dash of 2 pixels on (black) and two off each your aray would look like: {2,2} The pattern then repeats. If you wanted 5-wide dashes with a space of 2 pixels you would use {5,2}
In your code it would look like:
// Get Graphics object from chart
Graphics graph = e.ChartGraphics.Graphics;
PointF point1 = PointF.Empty;
PointF point2 = PointF.Empty;
// Set Maximum and minimum points
point1.X = -110;
point1.Y = -110;
point2.X = 122;
point2.Y = 122;
// Convert relative coordinates to absolute coordinates.
point1 = e.ChartGraphics.GetAbsolutePoint(point1);
point2 = e.ChartGraphics.GetAbsolutePoint(point2);
// Draw (dashed) connection line
float[] dashValues = { 4, 2 };
Pen dashPen= new Pen(Color.Yellow, 3);
dashPen.DashPattern = dashValues;
graph.DrawLine(dashPen, point1, point2);
I think you can accomplish this by changing the pen you use to draw your line.
So, replace the last 2 lines in your example with:
var pen = new Pen(Color.Yellow, 3);
pen.DashStyle = DashStyle.Dash;
graph.DrawLine(pen, point1, point2);
Pen has a public property that is defined as
public DashStyle DashStyle { get; set; }
you can set DasStyle.Dash if you want to draw a Dashed line.
Pen.DashPattern will do this. Look here for an example
In more modern C#:
var dottedPen = new Pen(Color.Gray, width: 1) { DashPattern = new[] { 1f, 1f } };
To answer this question regarding the generation of a dashed line using the code-behind:
Pen dashPenTest = new(Brushes.DodgerBlue, 1);
Line testLine = new()
{
Stroke = dashPenTest.Brush, //Brushes.Aqua,
StrokeThickness = dashPenTest.Thickness,//1,
StrokeDashArray = new DoubleCollection() { 8,4 },
X1 = 0,
X2 = canvas.Width,
Y1 = 10,
Y2 = 10
};
canvas.Children.Add(testLine);
This answer make use of the generation of a canvas in the xaml:
<Canvas x:Name ="canvas" Background="White" Height="300" Width="300">
The important method here is the "StrokeDashArray" that generates the dashes for the line drawn. More information is given here: Shape.StrokeDashArray

Categories