Simple 2d physics with polygons C# - c#

I have been working on a 2D physics engine using polygons.
And i am having trouble implementing the actual physics part. For a bit of background, i am not experienced at all when it comes to physics and therefor even if a found how to do the entire physics thing online, i would not be able to implement it into my project.
My goal is:
To have polygons fall with gravity.
Have weight drag etc.
Collision between multiple polygons.
What i have already made:
A way of displaying and creating multiple polygons.
Moving and rotating specified object(polygon).
Coeffients for drag, gravity and weight.
Hit boxes and visual boxes. (Visual boxes are what gets displayed and hit boxes are for physics)
A center point for every object. (So far is used for rotation)
A tick for when everything gets calculated. (Gametick/tickrate or whatever you wanna call it)
What i was not able to add / looking for:
Actual gravity.
Collision detection
Velocity for each object.
Collision between object.
Code snippets / how stuff works so far:
Beware that my code is janky and could be made better or more efficient.
Efficiency is not what im looking for!
Function for creating object:
public Object CreateNew(PointF[] hb, PointF[] vb, float rt, Color cl, bool gr, PointF ps)
{
Object obj = new Object
{
pos = ps,
rotation = rt,
offsets = vb,
hitBox = hb,
visBox = vb,
gravity = gr,
clr = cl,
};
#region center
List<Vector2> v2Points = new List<Vector2>();
foreach (PointF p in obj.offsets)
{
v2Points.Add(new Vector2(p.X, p.Y));
}
PointF point = ToPoint(Centroid(v2Points));
obj.center = new PointF(point.X, point.Y);
#endregion
return obj;
}
Function for changing position of object:
public Object ChangePosition(PointF pos, double rot, Object obj)
{
//////////////
int i = 0;
foreach (PointF p in obj.visBox)
{
float minPosX = (float)Math.Sqrt((Math.Pow(obj.center.X - pos.X, 2) + Math.Pow(0 - 0, 2)));
float minPosY = (float)Math.Sqrt((Math.Pow(obj.center.Y - pos.Y, 2) + Math.Pow(0 - 0, 2)));
obj.visBox[i] = new PointF(obj.offsets[i].X + pos.X, obj.offsets[i].Y + pos.Y);
i++;
}
i = 0;
foreach (PointF p in obj.hitBox)
{
float minPosX = (float)Math.Sqrt((Math.Pow(obj.center.X - pos.X, 2) + Math.Pow(0 - 0, 2)));
float minPosY = (float)Math.Sqrt((Math.Pow(obj.center.Y - pos.Y, 2) + Math.Pow(0 - 0, 2)));
obj.hitBox[i] = new PointF(obj.offsets[i].X + pos.X, obj.offsets[i].Y + pos.Y);
i++;
}
obj.pos = pos;
List<Vector2> v2Points = new List<Vector2>();
foreach (PointF p in obj.offsets)
{
v2Points.Add(new Vector2(p.X, p.Y));
}
PointF point = ToPoint(Centroid(v2Points));
obj.center = point;
List<Vector2> v2Points2 = new List<Vector2>();
foreach (PointF p in obj.hitBox)
{
v2Points2.Add(new Vector2(p.X, p.Y));
}
PointF point2 = ToPoint(Centroid(v2Points2));
obj.centerHitBox = point2;
obj.hitBox = RotatePolygon(obj.hitBox, obj.center, rotation * -1);
obj.visBox = RotatePolygon(obj.visBox, obj.center, rotation * -1);
obj.offsets = RotatePolygon(obj.offsets, obj.center, rotation * -1);
obj.hitBox = RotatePolygon(obj.hitBox, obj.center, rot);
obj.visBox = RotatePolygon(obj.visBox, obj.center, rot);
obj.offsets = RotatePolygon(obj.offsets, obj.center, rot);
rotation = rot;
return obj;
}
Pastebin link to object script:
https://pastebin.com/9SnG4vyj
I will provide more information or scripts if anybody needs it!

Related

Why isn't my perspective transform working

I am building a test 3D renderer in WinForms using the objects in System.Numerics such as Vector3 and Matrix4x4.
The object drawn is a point cloud, centered around (0,0,0), and rotated about the origin. Each node renders as dots on the screen. Here is what the 3D shape should look like
Fake Perspective
and more specifically when viewed from the front the perspective should be obvious with the blue dots that are further away from the eye to be at a smaller distance from the center
Fake Perspective
The pipeline is roughly as follows:
Rotation transformation
Matrix4x4 RY = Matrix4x4.CreateRotationY(ry);
Perspective transformation (fov=90, aspect=1.0f, near=1f, far=100f)
Matrix4x4 P = Matrix4x4.CreatePerspectiveFieldOfView(fov.Radians(), 1.0f, 1f, 100f);
Camera transformation
Matrix4x4 C = RY * P;
var node = Vector3.Transform(face.Nodes[i], C);
Project to 2D
Vector2 point = new Vector2(node.X, node.Y);
View transformation
Matrix3x2 S = Matrix3x2.CreateScale(height / scale, -height / scale);
Matrix3x2 T = Matrix3x2.CreateTranslation(width / 2f, height / 2f);
Matrix3x2 V = S*T
point = Vector2.Transform(point, V);
Pixel Coordinates & Render
PointF pixel = new PointF(point.X, point.Y);
e.Graphics.FillEllipse(brush,pixel.X - 2, pixel.Y - 2, 4, 4);
So what I am seeing is an orthographic projection.
Program Output
The blue nodes further away are not smaller as expected. Somehow the perspective transformation is being ignored.
So my question is my usage of Matrix4x4.CreatePerspectiveFieldOfView() correct in step #2? And is the projection from 3D to 2D in step #4 correct?
Steps #1, #5 and #6 seem to be working exactly as intended, my issue is with steps #2-#4 somewhere.
Example code to reproduce the issue
Form1.cs
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public Shape Object { get; set; }
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.Object = Shape.DemoShape1();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
float width = ClientSize.Width, height = ClientSize.Height;
float scale = 40f, fov = 90f;
Matrix4x4 RY = Matrix4x4.CreateRotationY(ry);
Matrix4x4 RX = Matrix4x4.CreateRotationX(rx);
Matrix4x4 P = Matrix4x4.CreatePerspectiveFieldOfView(fov.Radians(), 1.0f, 1f, 100f);
Matrix4x4 C = RY * RX * P;
Matrix3x2 S = Matrix3x2.CreateScale(
height / scale, -height / scale);
Matrix3x2 T = Matrix3x2.CreateTranslation(
width / 2f, height / 2f);
Matrix3x2 V = S * T;
using (var pen = new Pen(Color.Black, 0))
{
var arrow = new AdjustableArrowCap(4f, 9.0f);
pen.CustomEndCap = arrow;
using (var brush = new SolidBrush(Color.Black))
{
// Draw coordinate triad (omited)
// Each face has multiple nodes with the same color
foreach (var face in Object.Faces)
{
brush.Color = face.Color;
PointF[] points = new PointF[face.Nodes.Count];
for (int i = 0; i < points.Length; i++)
{
// transform nodes into draw points
var item = Vector4.Transform(face.Nodes[i], C);
var point = Vector2.Transform(item.Project(), V);
points[i] = point.ToPoint();
}
// Draw points as dots
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
for (int i = 0; i < points.Length; i++)
{
e.Graphics.FillEllipse(brush,
points[i].X - 2, points[i].Y - 2,
4, 4);
}
}
}
}
}
}
GraphicsExtensions.cs
public static class GraphicsExtensions
{
public static PointF ToPoint(this Vector2 vector)
=> new PointF(vector.X, vector.Y);
public static Vector2 Project(this Vector3 vector)
=> new Vector2(vector.X, vector.Y);
public static Vector2 Project(this Vector4 vector)
=> new Vector2(vector.X, vector.Y);
public static float Radians(this float degrees) => (float)(Math.PI/180) * degrees;
public static float Degrees(this float radians) => (float)(180/Math.PI) * radians;
}

Create mesh from an Object with Spatial Awareness Mesh Observer MHL2

I'm new here and also quite new at C#/Unity2019.4.14f with VS2019, MRTK V2.5.3, and Microsoft Hololens 2 programming. I would like to ask you for advice on a problem that I have not been able to solve for weeks. First of all, I would like to quickly explain what kind of problem it is. My task is to track an object that is in an examination cube with the Spatial Mesh and to represent its shape as well as possible.Explanation screen for the task description
The calculations of where the examination cube is located in space work without any problems. But for some reason, I cannot query the Spatial Awareness Mesh Observer. Anyway, no meshes seem to be present although they are visible.
Since I am at a complete loss and no one I have asked so far has been able to help me, I am publishing my code for this function below. Please bear with me, as I am still a beginner in writing code.
public void ReadAndDrawMesh(){
//Provide a list of the cube coordinates
Vector3[] CubeCoordinateList = new Vector3[24];
//Convert Local to World Coordinates
var localToWorld = transform.localToWorldMatrix;
Vector3 cubeWorldPos = Cube.transform.position; // Reading out the centre position
Vector3[] cubeVertices = Cube.GetComponent<MeshFilter>().mesh.vertices; //World coordinates?
List<Vector3> cubeWorldVertices = new List<Vector3>();
for (int i = 0; i <= (cubeVertices.Length) - 1; i++)
{
CubeCoordinateList[i] = localToWorld.MultiplyPoint3x4(cubeVertices[i]);
}
//CubeVerticies from Vector A[0] to E[4]
float normalX1 = CubeCoordinateList[4].x - CubeCoordinateList[0].x;
float normalY1 = CubeCoordinateList[4].y - CubeCoordinateList[0].y;
float normalZ1 = CubeCoordinateList[4].z - CubeCoordinateList[0].z;
float amount1 = Mathf.Sqrt((normalX1 * normalX1) + (normalY1 * normalY1) + (normalZ1 * normalZ1));
//Create a new vector
Vector3 direction1 = new Vector3(normalX1, normalY1, normalZ1);
direction1 = direction1 / amount1;
//CubeVerticies from Vector A[0] to B[2]
float normalX2 = CubeCoordinateList[2].x - CubeCoordinateList[0].x;
float normalY2 = CubeCoordinateList[2].y - CubeCoordinateList[0].y;
float normalZ2 = CubeCoordinateList[2].z - CubeCoordinateList[0].z;
float amount2 = Mathf.Sqrt((normalX2 * normalX2) + (normalY2 * normalY2) + (normalZ2 * normalZ2));
//Create a new vector
Vector3 direction2 = new Vector3(normalX2, normalY2, normalZ2);
direction2 = direction2 / amount2;
//CubeVerticies from Vector A[0] to D[3]
float normalX3 = CubeCoordinateList[3].x - CubeCoordinateList[0].x;
float normalY3 = CubeCoordinateList[3].y - CubeCoordinateList[0].y;
float normalZ3 = CubeCoordinateList[3].z - CubeCoordinateList[0].z;
float amount3 = Mathf.Sqrt((normalX3 * normalX3) + (normalY3 * normalY3) + (normalZ3 * normalZ3));
//Create a new vector
Vector3 direction3 = new Vector3(normalX3, normalY3, normalZ3);
direction3 = direction3 / amount3;
//From MRTK 2.5.3
// Use CoreServices to quickly get access to the IMixedRealitySpatialAwarenessSystem
var spatialAwarenessService = CoreServices.SpatialAwarenessSystem;
// Cast to the IMixedRealityDataProviderAccess to get access to the data providers
var dataProviderAccess = spatialAwarenessService as IMixedRealityDataProviderAccess;
var meshObserverName = "SpatialAwarenessMeshObserverProfile";
var MeshObserver = dataProviderAccess.GetDataProvider<IMixedRealitySpatialAwarenessMeshObserver>(meshObserverName);
foreach (SpatialAwarenessMeshObject meshObject in MeshObserver.Meshes.Values)
{
Vector3[] meshObjectarray = meshObject.Filter.mesh.vertices;
//Reading the Spatial Mesh of the room
foreach (Vector3 verticiesCoordinaten in meshObjectarray)
{
//List for the mesh coordinates that come out of the scalar product calculation
Vector3[] MeshPositionList = new Vector3[meshObjectarray.Length]; ;
//Determining Direction between verticiesCoordinates of MeshObject with Centre of Cube
var dir_vectorMesh = verticiesCoordinaten - cubeWorldPos;
//Calculating the scalar product of the coordinates
var result1 = Mathf.Abs(Vector3.Dot(dir_vectorMesh, direction1)) * 2;
var result2 = Mathf.Abs(Vector3.Dot(dir_vectorMesh, direction2)) * 2;
var result3 = Mathf.Abs(Vector3.Dot(dir_vectorMesh, direction3)) * 2;
// If scalar product Negative, then write it in the list
if (result1 > amount1 && result2 > amount2 && result3 > amount3)
{
MeshPositionList[meshObjectarray.Length] = verticiesCoordinaten;
}
//Creating a new visible mesh from the points in the list
Mesh mesh = new Mesh();
mesh.vertices = MeshPositionList;
mesh.Optimize();
Graphics.DrawMeshNow(mesh, Vector3.zero, Quaternion.identity);
mesh.RecalculateNormals();
}
}
}
I hope that one of you can help me and I look forward to any constructive answers. Thank you to everyone who reads this post and perhaps responds.
Thank You.

How to set Texture Coordinates properly for Mesh3D

I'm new to 3D programming and am having a terrible time getting my texture to fill my meshes properly. I've got it sizing correctly on the walls but the texture on the roof is running on an angle and is stretched out too far.
I have several methods to create the mesh but they are all eventually sent to AddTriangle method, where the TextureCoordinates are set.
public static void AddTriangle(this MeshGeometry3D mesh, Point3D[] pts)
{
// Create the points.
int index = mesh.Positions.Count;
foreach (Point3D pt in pts)
{
mesh.Positions.Add(pt);
mesh.TriangleIndices.Add(index++);
mesh.TextureCoordinates.Add(new Point(pt.X + pt.Z, 0 - pt.Y));
}
}
Here is how my material is set up.
imageBrush.ImageSource = new BitmapImage(new Uri("pack://application:,,,/Textures/shingles1.jpg"));
imageBrush.TileMode = TileMode.Tile;
imageBrush.ViewportUnits = BrushMappingMode.Absolute;
imageBrush.Viewport = new Rect(0, 0, 25, 25);
SidingColor = new DiffuseMaterial(imageBrush);
SidingColor.Color = RGB(89, 94, 100);
My texture looks like this:
And here is the results I'm getting.
That's as close as I could get after hours of fooling around and googling.
Whew that was a little more difficult than I anticipated.
Here are few resources that helped me find a solution.
How to convert a 3D point on a plane to UV coordinates?
From the link below I realized the above formula above formula was correct but for a right hand coordinate system. I converted it and that was the final step.
http://www.math.tau.ac.il/~dcor/Graphics/cg-slides/geom3d.pdf
Here is the code that works in case someone else has this question.
public static void AddTriangle(this MeshGeometry3D mesh, Point3D[] pts)
{
if (pts.Count() != 3) return;
//use the three point of the triangle to calculate the normal (angle of the surface)
Vector3D normal = CalculateNormal(pts[0], pts[1], pts[2]);
normal.Normalize();
//calculate the uv products
Vector3D u;
if (normal.X == 0 && normal.Z == 0) u = new Vector3D(normal.Y, -normal.X, 0);
else u = new Vector3D(normal.X, -normal.Z, 0);
u.Normalize();
Vector3D n = new Vector3D(normal.Z, normal.X, normal.Y);
Vector3D v = Vector3D.CrossProduct(n, u);
int index = mesh.Positions.Count;
foreach (Point3D pt in pts)
{
//add the points to create the triangle
mesh.Positions.Add(pt);
mesh.TriangleIndices.Add(index++);
//apply the uv texture positions
double u_coor = Vector3D.DotProduct(u, new Vector3D(pt.Z,pt.X,pt.Y));
double v_coor = Vector3D.DotProduct(v, new Vector3D(pt.Z, pt.X, pt.Y));
mesh.TextureCoordinates.Add(new Point(u_coor, v_coor));
}
}
private static Vector3D CalculateNormal(Point3D firstPoint, Point3D secondPoint, Point3D thirdPoint)
{
var u = new Point3D(firstPoint.X - secondPoint.X,
firstPoint.Y - secondPoint.Y,
firstPoint.Z - secondPoint.Z);
var v = new Point3D(secondPoint.X - thirdPoint.X,
secondPoint.Y - thirdPoint.Y,
secondPoint.Z - thirdPoint.Z);
return new Vector3D(u.Y * v.Z - u.Z * v.Y, u.Z * v.X - u.X * v.Z, u.X * v.Y - u.Y * v.X);
}

Scaling a sprite size using a vector2 Monogame C#

so I'm currently working on resolution independence for my game, and I'm testing it out on a sword image. The position changing is working, but whenever I start doing the size I end up with a blank screen.
These are the functions I run to get the new position and size of the sprite.
private static float CalcRatio(Vector2 size)
{
return size.Y / size.X;
}
public static Vector2 CalculateNewPos(Vector2 refPos, Vector2 refScreenSize, Vector2 currentScreenSize)
{
return new Vector2((refPos.X / refScreenSize.X) * currentScreenSize.X,
(refPos.Y / refScreenSize.Y) * currentScreenSize.Y);
}
public static Vector2 CalculateNewSize(Vector2 refSize, Vector2 refScreenSize, Vector2 currenScreenSize)
{
float origRatio = CalcRatio(refSize);
float perW = refSize.X * 100f / refScreenSize.X;
float newW = perW / 100f * currenScreenSize.X;
float newH = newW * origRatio;
return new Vector2(newW, newH);
}
In the Initialization function in Game1 I run this code:
graphics.PreferredBackBufferHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
graphics.PreferredBackBufferWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
swordPosition = CalculateNewPos(swordRefPosition, new Vector2(1920, 1080), new Vector2(GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width, GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height));
swordSize = CalculateNewSize(swordRefSize, new Vector2(1920, 1080), new Vector2(GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width, GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height));
In the load function I run this:
swordTexture = content.Load<Texture2D>("SOLDIER_Sword");
swordPosition = new Vector2(300, 0);
swordRefSize = new Vector2(557, 490);
swordSize = new Vector2(557, 490);
swordRefPosition = new Vector2(300, 0);
swordColor = Color.White;
sword = new StaticSprite(swordTexture, swordPosition, swordSize, swordColor);
In update everytime the screen resolution changes (I have buttons set to do that) this:
graphics.PreferredBackBufferHeight = setHeight;
graphics.PreferredBackBufferWidth = setWidth;
graphics.ApplyChanges();
swordPosition = CalculateNewPos(swordRefPosition, new Vector2(1920, 1080), new Vector2(setWidth, setHeight));
swordSize = CalculateNewSize(swordRefSize, new Vector2(1920, 1080), new Vector2(setWidth, setHeight));
And in draw:
batch.Draw(swordTexture, swordPosition, null, swordColor, 0f, Vector2.Zero, swordSize, SpriteEffects.None, 0f);
Sorry there is so much code, I'm really stumped and can't pinpoint where it's going wrong, so I just included everything that changes the variables.
Thank you so much for taking the time to look through this.
Note that current display mode is only meaningful if you are fullscreen. It's the VIEWPORT that tells you the width/height of spritebatch's projection matrix.
That said, you should really look into Bjarke's post, because you don't usually want to be rescaling every individual item.

Drawing Bezier curves in MonoGame (XNA) produces scratchy lines

I recently got into using MonoGame, and I love the library.
However, I seem to be having some issues with drawing bezier curves
The result that my code produces looks something like this
Look bad, no?
The lines aren't smooth at all.
Let me show you some of the code:
//This is what I call to get all points between which to draw.
public static List<Point> computeCurvePoints(int steps)
{
List<Point> curvePoints = new List<Point>();
for (float x = 0; x < 1; x += 1 / (float)steps)
{
curvePoints.Add(getBezierPointRecursive(x, pointsQ));
}
return curvePoints;
}
//Calculates a point on the bezier curve based on the timeStep.
private static Point getBezierPointRecursive(float timeStep, Point[] ps)
{
if (ps.Length > 2)
{
List<Point> newPoints = new List<Point>();
for (int x = 0; x < ps.Length-1; x++)
{
newPoints.Add(interpolatedPoint(ps[x], ps[x + 1], timeStep));
}
return getBezierPointRecursive(timeStep, newPoints.ToArray());
}
else
{
return interpolatedPoint(ps[0], ps[1], timeStep);
}
}
//Gets the linearly interpolated point at t between two given points (without manual rounding).
//Bad results!
private static Point interpolatedPoint(Point p1, Point p2, float t)
{
Vector2 roundedVector = (Vector2.Multiply(p2.ToVector2() - p1.ToVector2(), t) + p1.ToVector2());
return new Point((int)roundedVector.X, (int)roundedVector.Y);
}
//Method used to draw a line between two points.
public static void DrawLine(this SpriteBatch spriteBatch, Texture2D pixel, Vector2 begin, Vector2 end, Color color, int width = 1)
{
Rectangle r = new Rectangle((int)begin.X, (int)begin.Y, (int)(end - begin).Length() + width, width);
Vector2 v = Vector2.Normalize(begin - end);
float angle = (float)Math.Acos(Vector2.Dot(v, -Vector2.UnitX));
if (begin.Y > end.Y) angle = MathHelper.TwoPi - angle;
spriteBatch.Draw(pixel, r, null, color, angle, Vector2.Zero, SpriteEffects.None, 0);
}
//DrawLine() is called as following. "pixel" is just a Texture2D with a single black pixel.
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
for(int x = 0; x < curvePoints.Count-1; x++)
{
DrawExtenstion.DrawLine(spriteBatch, pixel, curvePoints[x].ToVector2(), curvePoints[x + 1].ToVector2(), Color.Black, 2);
}
spriteBatch.End();
base.Draw(gameTime);
}
I managed to make the line a bit smoother by adding some manual Math.Round() calls to my interpolatedPoint method
//Gets the linearly interpolated point at t between two given points (with manual rounding).
//Better results (but still not good).
private static Point interpolatedPoint(Point p1, Point p2, float t)
{
Vector2 roundedVector = (Vector2.Multiply(p2.ToVector2() - p1.ToVector2(), t) + p1.ToVector2());
return new Point((int)Math.Round(roundedVector.X), (int)Math.Round(roundedVector.Y));
}
This produces the following result:
I had to remove one picture since Stackoverflow doesn't let me use more than two links
Are there any ways I can get this curve to be absolutely smooth?
Perhaps there is a problem with the DrawLine method?
Thanks in advance.
EDIT:
Okay, I managed to make the curve look a lot better by doing all the calculations with Vector2Ds and only converting it to a Point at the moment that it needs to be drawn
It still isn't perfect though :/
As Mike 'Pomax' Kamermans said,
it seems to have been a problem with the 2D surface not allowing subpixel drawing and thus causing rounding issues
Following craftworkgames' advice, I adapted the algorithm to draw the curve in 3D using a BasicEffect. This also allows for antialiasing, which smoothes out the curve a lot.
The result is the following:
A lot better!
Thank you very much for the helpful advice!
EDIT:
Here is the code I used for doing this.
I would also like to add that this webpage (http://gamedevelopment.tutsplus.com/tutorials/create-a-glowing-flowing-lava-river-using-bezier-curves-and-shaders--gamedev-919) helped me a lot while writing this code.
Also, please note that some of the names I used for defining the methods might not really make sense or can be confusing. This was something I quickly put together on an evening.
//Used for generating the mesh for the curve
//First object is vertex data, second is indices (both as arrays)
public static object[] computeCurve3D(int steps)
{
List<VertexPositionTexture> path = new List<VertexPositionTexture>();
List<int> indices = new List<int>();
List<Vector2> curvePoints = new List<Vector2>();
for (float x = 0; x < 1; x += 1 / (float)steps)
{
curvePoints.Add(getBezierPointRecursive(x, points3D));
}
float curveWidth = 0.003f;
for(int x = 0; x < curvePoints.Count; x++)
{
Vector2 normal;
if(x == 0)
{
//First point, Take normal from first line segment
normal = getNormalizedVector(getLineNormal(curvePoints[x+1] - curvePoints[x]));
}
else if (x + 1 == curvePoints.Count)
{
//Last point, take normal from last line segment
normal = getNormalizedVector(getLineNormal(curvePoints[x] - curvePoints[x-1]));
} else
{
//Middle point, interpolate normals from adjacent line segments
normal = getNormalizedVertexNormal(getLineNormal(curvePoints[x] - curvePoints[x - 1]), getLineNormal(curvePoints[x + 1] - curvePoints[x]));
}
path.Add(new VertexPositionTexture(new Vector3(curvePoints[x] + normal * curveWidth, 0), new Vector2()));
path.Add(new VertexPositionTexture(new Vector3(curvePoints[x] + normal * -curveWidth, 0), new Vector2()));
}
for(int x = 0; x < curvePoints.Count-1; x++)
{
indices.Add(2 * x + 0);
indices.Add(2 * x + 1);
indices.Add(2 * x + 2);
indices.Add(2 * x + 1);
indices.Add(2 * x + 3);
indices.Add(2 * x + 2);
}
return new object[] {
path.ToArray(),
indices.ToArray()
};
}
//Recursive algorithm for getting the bezier curve points
private static Vector2 getBezierPointRecursive(float timeStep, Vector2[] ps)
{
if (ps.Length > 2)
{
List<Vector2> newPoints = new List<Vector2>();
for (int x = 0; x < ps.Length - 1; x++)
{
newPoints.Add(interpolatedPoint(ps[x], ps[x + 1], timeStep));
}
return getBezierPointRecursive(timeStep, newPoints.ToArray());
}
else
{
return interpolatedPoint(ps[0], ps[1], timeStep);
}
}
//Gets the interpolated Vector2 based on t
private static Vector2 interpolatedPoint(Vector2 p1, Vector2 p2, float t)
{
return Vector2.Multiply(p2 - p1, t) + p1;
}
//Gets the normalized normal of a vertex, given two adjacent normals (2D)
private static Vector2 getNormalizedVertexNormal(Vector2 v1, Vector2 v2) //v1 and v2 are normals
{
return getNormalizedVector(v1 + v2);
}
//Normalizes the given Vector2
private static Vector2 getNormalizedVector(Vector2 v)
{
Vector2 temp = new Vector2(v.X, v.Y);
v.Normalize();
return v;
}
//Gets the normal of a given Vector2
private static Vector2 getLineNormal(Vector2 v)
{
Vector2 normal = new Vector2(v.Y, -v.X);
return normal;
}
//Drawing method in main Game class
//curveData is a private object[] that is initialized in the constructor (by calling computeCurve3D)
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
var camPos = new Vector3(0, 0, 0.1f);
var camLookAtVector = Vector3.Forward;
var camUpVector = Vector3.Up;
effect.View = Matrix.CreateLookAt(camPos, camLookAtVector, camUpVector);
float aspectRatio = graphics.PreferredBackBufferWidth / (float)graphics.PreferredBackBufferHeight;
float fieldOfView = MathHelper.PiOver4;
float nearClip = 0.1f;
float farClip = 200f;
//Orthogonal
effect.Projection = Matrix.CreateOrthographic(480 * aspectRatio, 480, nearClip, farClip);
foreach (var pass in effect.CurrentTechnique.Passes)
{
pass.Apply();
effect.World = Matrix.CreateScale(200);
graphics.GraphicsDevice.DrawUserIndexedPrimitives(PrimitiveType.TriangleList,
(VertexPositionTexture[])curveData[0],
0,
((VertexPositionTexture[])curveData[0]).Length,
(int[])curveData[1],
0,
((int[])curveData[1]).Length/3);
}
base.Draw(gameTime);
}
Also, this image may be able to show what the code does a little bit better
So, I needed something like this working with SpriteBatch, so I poked around at the original code a bit (with the Point -> Vector2 and rounding changes.
If you render every other segment as a different color, and with a large enough width and low enough steps, you can see why it resulted in jagged lines with larger values of width. It turns out the lines go past where they should end!
Lines going past their end:
This is because the DrawLine function adds width to length of the segment. However, without this, you see a bunch of disconnected segments for anything that actually curves.
Lines being disconnected:
There's probably some math you can do to get the appropriate value to add here, based on the angle of the connecting points. I don't know math well enough for that, so I'm just using a fixed value for them all. (10 seems to be the sweet spot for the image I posted, although it isn't perfect due to the low step count.)
(The following is DrawLine adjusted with the width being added, to using a constant instead.)
// Method used to draw a line between two points.
public static void DrawLine(this SpriteBatch spriteBatch, Texture2D pixel, Vector2 begin, Vector2 end, Color color, int width = 1)
{
Rectangle r = new Rectangle((int)begin.X, (int)begin.Y, (int)(end - begin).Length() + 10, width);
Vector2 v = Vector2.Normalize(begin - end);
float angle = (float)Math.Acos(Vector2.Dot(v, -Vector2.UnitX));
if (begin.Y > end.Y) angle = MathHelper.TwoPi - angle;
spriteBatch.Draw(pixel, r, null, color, angle, Vector2.Zero, SpriteEffects.None, 0);
}

Categories