Visual C# bezier drawing problems - c#

I having an issue in a drawing application what I developing, in what bezier curves don't work as they should.
I have this code line:
g.DrawBezier(pen, points[i - 1], points[i], points[i], points[i + 1]);
It does his job nice, but for some reason it also paints an extra line what is very unhelpful...
As you can see, for some reason, an awful line appears on top of the spline.
Can someone please help me?
for (int i = 0; i < points.Count - 1; i++) {
pen.Color = colors[i];
pen.Width = widths[i];
if (visible[i] == true) {
g.DrawBezier(pen, points[i - 1], points[i], points[i], points[i + 1]);
if (spoints == true) {
g.DrawEllipse(new Pen(Color.LimeGreen, 5), points[i].X - 1, points[i].Y - 1, 2, 2);
if (spositions == true) {
g.DrawString(points[i].X.ToString() + ", " + points[i].Y.ToString(), new Font("Courier", 8), pen.Brush, points[i]);
}
}
}
if (sskeleton == true)
{
g.DrawLine(new Pen(Color.Magenta, 1), points[i].X, points[i].Y, points[i + 1].X, points[i + 1].Y);
}
}

I think here's what's happening.
Say the points are
List<Point> points = new List<Point>() {
new Point(50, 50),
new Point(150, 150),
new Point(150, 250),
new Point(300, 300),
new Point(400, 300)};
The first bezier curve is drawn
The green dots are the actual points. Notice that the curve doesn't actually go through the second point.
The next curve is drawn.
This curve starts at the second point (not where the last curve went through).
Final plot:
The middle two points in the DrawBezier function are control points, so the curve isn't guaranteed to go through those two points. https://msdn.microsoft.com/en-us/library/a7h66bsy(v=vs.110).aspx
Perhaps what you're trying to accomplish could be done with DrawBeziers. https://msdn.microsoft.com/en-us/library/ds101091(v=vs.110).aspx
Another option would be to start the next bezier curve at the end point of the last one (instead of at the control point of the last one).
If you want a curve that goes through all of the points, try DrawCurve(pen, points).

Related

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.

Arcs and line segments detection from collection of points

I am preparing to create a program to detect arcs and line segments from the collection of points (x,y) which follow each other and generally, they create a closed polygon.
I just dont know where to start.
Maybe someone knows "ready to go" library (can be paid) which do such a think for me? Or maybe some propositions of algorithms that are relatively easy to implement? I am looking for any advices.
If your points don't precisely fall on the arcs and segments, but you're trying to find the closest fit, see: 'https://en.wikipedia.org/wiki/Curve_fitting'.
If your points follow exactly arcs or line segments, maybe this closed solution might work for you.
It assumes arcs are made out of at least 4 points. You can create an arc with any 3 points, so there is no way to tell if they are supposed to be an arc or line segment (unless you use an angle threshold). With 4 points you can compare if points (0,1,2) and (0,1,3) are part of the same arc.
When creating arc objects from 3 points, internally it calculates the radius and center to be able to compare them. To find a circle from 3 points: https://math.stackexchange.com/questions/213658/get-the-equation-of-a-circle-when-given-3-points
PolyCurve ArcsAndLines(List<Point3d> points)
{
var curve = new PolyCurve();
Arc current = Arc.Unset;
for (int i = 0; i < points.Count - 1; i++)
{
var areEqual = false;
if (i + 3 < points.Count)
{
var arcA = new Arc(points[i], points[i + 1], points[i + 2]);
var arcB = new Arc(points[i], points[i + 1], points[i + 3]);
areEqual = AreEqual(arcA, arcB);
}
if (areEqual)
{
var start = current == Arc.Unset ? points[i] : current.StartPoint;
current = new Arc(start, points[i + 1], points[i + 3]);
}
else
{
if (current != Arc.Unset)
{
curve.Append(current);
current = Arc.Unset;
i++;
}
else
{
curve.Append(new Line(points[i], points[i + 1]));
}
}
}
return curve;
}
bool AreEqual(Arc a, Arc b)
{
const double tol = 0.001;
bool sameRadius = Math.Abs(a.Radius - b.Radius) < tol;
if (!sameRadius) return false;
bool sameCenter = a.Center.DistanceTo(b.Center) < tol;
return sameCenter;
}
'curve' is a list containing line and arc segments.
It should work with open polylines and polygons (with end point being the same as start). If the start of a polygon lies in the middle of an arc, it will be split into two arcs.
A regular polygon (resulting in a circle) will not work properly since you'll be defining an arc with the same start and end points.

How to check rectangle collision on a line

I have a line drawn from two rectangles, spanning from xpos, ypos to xpos2, ypos2. I'm trying to detect if a rectangle (stored in 4 arrays of X/Y pos and random speed in those two directions) collides with the line.
I've tried (Vector2.Distance(new Vector2(xpos + 13, ypos + 13), new Vector2(EnX[index], EnY[index])) + Vector2.Distance(new Vector2(xpos2 + 13, ypos2 + 13), new Vector2(EnX[index], EnY[index])) == Vector2.Distance(new Vector2(xpos + 13, ypos + 13), new Vector2(xpos2 + 13, ypos2 + 13))) in a if statement, but that doesn't work.
EDIT: I've now tried
m = (ypos2 + 13 - ypos + 13) / (xpos2 + 13 - xpos + 13);
b = ((m * xpos2 + 13) - ypos2 + 13);
if (EnY[index] == m * EnX[index] + b)
Where Xpos/2 and ypos/2 are the line starting points. EnX[] and EnY[] are the "enemy" X and Y positions, where I'm trying to check if they're hitting a line.
I assume that you have the rectangle's corner positions as 2d vectors:
Vector2[] corners;
The rectangle intersects with the line if any two corner points lie on the opposite side of the line. To evaluate the side, we need the line's normal (the slope approach you tried may fail for vertical lines):
Vector2 normal = new Vector2D(ypos2 - ypos, xpos - xpos2);
We can then use the sign of the dot product to evaluate the side:
Vector2 lineStart = new Vector2(xpos, ypos);
//we don't know yet on which side of the line the rectangle lies
float rectangleSide = 0;
foreach(Vector2 corner in corners)
{
//cornerSide will be positive if the corner is on the side the normal points to,
//zero if the corner is exactly on the line, and negative otherwise
float cornerSide = Vector2.Dot(corner - lineStart, normal);
if(rectangleSide == 0)
//first evaluated corner or all previous corners lie exactly on the line
rectangleSide = cornerSide;
else
if(cornerSide != 0 && //ignore corners on the line
(cornerSide > 0) != (rectangleSide > 0)) //different sides
return true; //rectangle intersects with line
}
return false; //rectangle does not intersect with line

I have a voxel engine that I am trying to optimize with Don't Repeat Yourself. Is there any way to fix this long-running mess?

For every single block type, I have to repeat a section of code that generates each face multiple times. I know there has to be a better way, but I did try to place the sections into a function per face and it errored out hard, even using a ref.
if (block.blockType == 1 && top == 0)
{
vertexIndex = vertices.Count;
vertices.Add(new Vector3(x, y + 1, z));
vertices.Add(new Vector3(x, y + 1, z + 1));
vertices.Add(new Vector3(x + 1, y + 1, z + 1));
vertices.Add(new Vector3(x + 1, y + 1, z));
// first triangle for the block top
triangles.Add(vertexIndex);
triangles.Add(vertexIndex + 1);
triangles.Add(vertexIndex + 2);
// second triangle for the block top
triangles.Add(vertexIndex + 2);
triangles.Add(vertexIndex + 3);
triangles.Add(vertexIndex);
// add UV
uvs.Add(new Vector2 (0.125f, 0.0f));
uvs.Add(new Vector2 (0.25f, 0.0f));
uvs.Add(new Vector2 (0.25f, 0.125f));
uvs.Add(new Vector2 (0.125f, 0.125f));
}
This is a typical section (sans StackOverflow formatting) - what could I do with it?
You should handle your 3 arrays, (Vertex / vertex indexes / uv coords) within a single flat array, that you pre-fill with 0 before filling it with actual values.
Performances would skyrocket.
I wonder if your vertex index won't contain each time the same sequences. You could only store ONE vertexIndex instead of the 6 numbers (+0 +1 +2 +2 +3 +0). But maybe you want to have the indexes at hand for webgl.
.
To factorize, i would make 6 of functions like this one :
Vertices.prototype.AddVerticesUP = function(x, y, z) {
var currIndex = this.lastIndex;
var vertArr = this.verticeArray;
vertArr[currIndex]=vertArr[currIndex+3]=
vertArr[currIndex+6]=vertArr[currIndex+9] = x;
vertArr[currIndex+1]=vertArr[currIndex+4]=
vertArr[currIndex+7]=vertArr[currIndex+10] = y;
vertArr[currIndex+2]=vertArr[currIndex+5]=
vertArr[currIndex+8]=vertArr[currIndex+11] =z;
// first point
vertArr[currIndex+1]++;
// second
vertArr[currIndex+4]++; vertArr[currIndex+5]++;
// third
vertArr[currIndex+6]++;vertArr[currIndex+7]++;vertArr[currIndex+8]++;
// fourth
vertArr[currIndex+9]++;vertArr[currIndex+10]++;
this.lastIndex +=12;
}
then build an array out of them :
Vertices.prototype.AddVertices= [ Vertices.prototype.AddVerticesUP,
Vertices.prototype.AddVerticesDOWN,
Vertices.prototype.AddVerticesLEFT,
Vertices.prototype.AddVerticesRIGHT,
Vertices.prototype.AddVerticesFRONT,
Vertices.prototype.AddVerticesBACK] ;
To use that array, if dir is the direction, just do :
Vertices.AddVertices [dir] ( x,y,z );
For your uv coordinates, you can simplify also, since all coordinates are axis aligned and square. Signature could be : uvs.addSquareUVs( baseU, baseV, size ). But if guess right, the coordinates depends only on side + block type. So you could make 6 functions with the same mechanism as above, and the signature would be uvs.addUV [dir] (block type) or uvs.addUV [dir] [block type] () .
now the 6 if/else for each side, multiplied by the number of block type can be greatly simplified to just this code :
vertexIndex = vertices.Count;
vertices.AddVertices [direction] ( x, y, z);
triangles.AddSquareStartingAt(vertexIndex);
uvs.addSquareUVs [direction] [block.blockType] ();
For vertex adding and uv coordinates you could define static collections somewhere:
public static readonly Vector3[] Corners = new Vector3[]{new Vector3(0, 1, 0), new Vector3(0, 1, 1), new Vector3(1, 1, 1), new Vector3(1, 1, 0)};
public static readonly Vector2[] UVCorners = new Vector2[]{new Vector2(0, 0), new Vector2(0.125f, 0), new Vector2(0.125f, 0.125f), new Vector2(0, 0.125f)};
And then you can make the section more compact:
if (block.blockType == 1 && top == 0)
{
vertexIndex = vertices.Count;
Vector3 origin = new Vector3(x, y, z);
foreach (Vector3 corner in Corners) vertices.Add(origin + corner);
// first triangle for the block top
triangles.Add(vertexIndex);
triangles.Add(vertexIndex + 1);
triangles.Add(vertexIndex + 2);
// second triangle for the block top
triangles.Add(vertexIndex + 2);
triangles.Add(vertexIndex + 3);
triangles.Add(vertexIndex);
// add UV
Vector2 uvOrigin = new Vector2(0.125f, 0);
foreach (Vector2 uvCorner in UVCorners) uvs.Add(uvOrigin + uvCorner);
}

How to draw bezier curve by several points?

I have several points, and I try to draw Bezier curve using code below
PathFigure pf = new PathFigure(points.From, ps, false); //ps - list of Bezier segments
PathFigureCollection pfc = new PathFigureCollection();
pfc.Add(pf);
var pge = new PathGeometry();
pge.Figures = pfc;
Path p = new Path();
p.Data = pge;
p.Stroke = new SolidColorBrush(Color.FromRgb(244, 111, 011));
My Bezier segments look like this
1,2,3 points - first segment
3,4,5 points - second
5,6,7.. ..
But I got this strange curve (here is 3 big (Nodes) and 7 small ellipse (is my points)):
The line you're getting is the union of three distinct Bezier curves - one for each group of three points. (One for each "Bezier segment"?)
If you want a single smooth curve, you need to pass your 9 (or more) points as a single collection of points (single "Bezier segment"?), not as groups of three points.
Edit: Apparently BezierSegment only supports three points, so no wonder this doesn't work. Even 'PolyBezierSegment' just gives a collection of Bezier segments rather than a single smooth Bezier...
So since WPF doesn't give you anything useful, I knocked something together using the maths here. It's a numeric solution, but it seems to be pretty performant even with enough points to look nice and smooth:
PolyLineSegment GetBezierApproximation(Point[] controlPoints, int outputSegmentCount)
{
Point[] points = new Point[outputSegmentCount + 1];
for (int i = 0; i <= outputSegmentCount; i++)
{
double t = (double)i / outputSegmentCount;
points[i] = GetBezierPoint(t, controlPoints, 0, controlPoints.Length);
}
return new PolyLineSegment(points, true);
}
Point GetBezierPoint(double t, Point[] controlPoints, int index, int count)
{
if (count == 1)
return controlPoints[index];
var P0 = GetBezierPoint(t, controlPoints, index, count - 1);
var P1 = GetBezierPoint(t, controlPoints, index + 1, count - 1);
return new Point((1 - t) * P0.X + t * P1.X, (1 - t) * P0.Y + t * P1.Y);
}
Using this,
private void Grid_Loaded(object sender, RoutedEventArgs e)
{
Point[] points = new[] {
new Point(0, 200),
new Point(0, 0),
new Point(300, 0),
new Point(350, 200),
new Point(400, 0)
};
var b = GetBezierApproximation(points, 256);
PathFigure pf = new PathFigure(b.Points[0], new[] { b }, false);
PathFigureCollection pfc = new PathFigureCollection();
pfc.Add(pf);
var pge = new PathGeometry();
pge.Figures = pfc;
Path p = new Path();
p.Data = pge;
p.Stroke = new SolidColorBrush(Color.FromRgb(255, 0, 0));
((Grid)sender).Children.Add(p);
}
gives
Since each of your curves has one control point (a point that influences the curve but isn't necessarily on the curve), you're using quadratic Bézier curves.
If you want to draw two quadratic curves that share an endpoint, and you want the joint to appear smooth, the control points on each side of the shared endpoint must be collinear with the endpoint. That is, the two control points and the endpoint between them must all lie on a straight line. Example:
The solid black discs are the endpoints. The hollow circles are the control points. The solid black line is the curve. The dotted lines show that each endpoint is collinear (on a straight line with) the control point on either side.

Categories