I suggest you create a class to handle the model-to-pixel conversions (and in reverse) instead of a function D2F(). A class it going to give you a lot more flexibility and it will retain a state of the current scaling values
public class Canvas
{
public Canvas()
{
Target = new Rectangle(0, 0, 1, 1);
Scale = 1;
}
public Rectangle Target { get; set; }
public float Scale { get; set; }
public void SetModelBounds(float width, float height)
{
Scale = Math.Min(Target.Width / width, Target.Height / height);
}
public PointF ToPixel(Vector2 point)
{
var center = new PointF(Target.Left + Target.Width / 2, Target.Top + Target.Height / 2);
return new PointF(center.X + Scale * point.X, center.Y - Scale * point.Y);
}
public Vector2 FromPixel(Point pixel)
{
var center = new PointF(Target.Left + Target.Width / 2, Target.Top + Target.Height / 2);
return new Vector2((pixel.X - center.X) / Scale, -(pixel.Y - center.Y) / Scale);
}
}
This is setup for each paint event by calling for example
Canvas.Target = this.ClientRectangle;
Canvas.SetModelBounds(2f, 2f);
The above code is going to place coordinates (-1,-1) on the bottom left of the form surface, and (1,1) on the top right. The pixels per model unit are kept the same for x-axis and y-axis (see Canvas.Scale).
Now for the drawing, also use am Entity class to hold the drawing geometry, and various other properties such as color, and if the shape is closed or not. Note that if it defines a Render(Graphics g, Canvas canvas) method, then it can be called by the form and each entity can handle its own drawing (for the most modular design)
Here is an example:
public class Entity
{
Entity(bool closed, Color color, params Vector2[] path)
{
Color = color;
Path = new List<Vector2>(path);
Closed = closed;
}
public Color Color { get; set; }
public List<Vector2> Path { get; }
public bool Closed { get; set; }
public void Render(Graphics g, Canvas canvas)
{
using (Pen pen = new Pen(Color, 1))
{
var points = Path.Select(pt => canvas.ToPixel(pt)).ToArray();
if (Closed)
{
g.DrawPolygon(pen, points);
}
else
{
g.DrawLines(pen, points);
}
}
}
public static Entity Triangle(Color color, Vector2 center, float width, float height)
{
return new Entity(true, color, new Vector2[] {
new Vector2(center.X-width/2, center.Y - height/3),
new Vector2(center.X+width/2, center.Y - height/3),
new Vector2(center.X, center.Y+2*height/3) });
}
public static Entity Rectange(Color color, Vector2 center, float width, float height)
{
return new Entity(true, color, new Vector2[] {
new Vector2(center.X-width/2, center.Y-height/2),
new Vector2(center.X+width/2, center.Y-height/2),
new Vector2(center.X+width/2, center.Y+height/2),
new Vector2(center.X-width/2, center.Y+height/2) });
}
public static Entity Polygon(Color color, params Vector2[] path)
=> new Entity(true, color, path);
public static Entity Polyline(Color color, params Vector2[] path)
=> new Entity(false, color, path);
}
The above is used in the form as follows
public partial class Form1 : Form
{
public Canvas Canvas { get; }
public List<Entity> Entities { get; }
public Form1()
{
InitializeComponent();
Entities = new List<Entity>();
Canvas = new Canvas();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.Resize += (s, ev) => Invalidate();
this.Paint += (s, ev) =>
{
ev.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
Canvas.Target = ClientRectangle;
Canvas.SetModelBounds(2f, 2f);
foreach (var item in Entities)
{
item.Render(ev.Graphics, Canvas);
}
};
Entities.Add( .. )
}
}
As you can see the Paint event calls the Render() method for each entity.
In fact, you can generalize this model using an interface IRender. In the example below besides Entity that implements the interface, I define a class to draw the coordinate axis called Axes.
public interface IRender
{
void Render(Graphics g, Canvas canvas);
}
public class Axes : IRender
{
public void Render(Graphics g, Canvas canvas)
{
PointF origin = canvas.ToPixel(Vector2.Zero);
PointF xpoint = canvas.ToPixel(Vector2.UnitX);
PointF ypoint = canvas.ToPixel(Vector2.UnitY);
using (Pen pen = new Pen(Color.Black, 0))
{
pen.CustomEndCap = new AdjustableArrowCap(2f, 5f);
g.DrawLine(pen, origin, xpoint);
g.DrawLine(pen, origin, ypoint);
}
}
}
public class Entity : IRender
{
...
}
and now you can draw with quite a bit of varying things on the screen with the above framework. Here is an example that draws the axis (of unit size) and a few sample entities.

Entities.Add(Entity.Polygon(Color.Orange, Vector2.Zero, -Vector2.UnitX, -Vector2.One));
Entities.Add(Entity.Rectange(Color.Blue, new Vector2(0.5f, 0.5f), 0.25f, 0.25f));
Entities.Add(Entity.Triangle(Color.Red, new Vector2(0.5f, 0.75f), 0.25f, 0.25f));
Entities.Add(Entity.Polyline(Color.Magenta,
new Vector2(0f, -0.2f), new Vector2(0f, -0.4f), new Vector2(0.2f, -0.4f),
new Vector2(0.2f, -0.2f), new Vector2(0.4f, -0.2f), new Vector2(0.4f, -0.4f)));