## Introduction

During the development of a videogame skeleton, one of my biggest concerns was the drawing of a large interstellar map. Since it was just a skeleton, implementing 3D graphics with OpenGL or Direct3D was not an option. I soon discovered that GDI+ could very well fit to my needs. Next I had to face mainly two issues:

- While .NET panels reason in terms of "pixels", my map was based on some "light years" metrical units.
- I had to imagine an algorithm to easily paint a sphere (or spheroid) on that map

The included code shows how I solved those problems. It could be helpful to show how easy is to add graphics and shapes using GDI+ and .NET.

## Conversion between pixel geometry and logical-units geometry

When we want to display something using GDI+, we must reason in terms of pixels. The `Graphics`

object can be obtained by calling the `CreateGraphics`

method from any inherited WinForm control. So, for instance, a `Panel`

is an optimal choice as a drawing surface, much better than the form itself. When a `Graphics`

object is initialized, it's geometry is based on the pixel displayed, being (0,0) the left-upper corner of it.

If I must draw a line, say, from the point (10,10) to the point (30,30) I must call:

Graphics panelGraphics = this.panel.CreateGraphics();
panelGraphics.DrawLine(new Pen(Color.Black,0.2F),10,10,30,30);

Since every method and property of GDI+ is based on pixels, drawing items in terms of some other units of measure implies a conversion. Also, when we use "real world" formulae, they must be adapted to the "pixel" logic. Instead of spreading the conversions throughout the code, a class is what is needed.

We could use the .NET library class `Graphics.PageScale`

to do so, since there are overloaded Graphics methods that accept float numbers (*measure units*) as input instead of integer numbers (*pixel*). But I preferred to write a class that has the same functions of `PageScale`

, but it's slightly different in concept. You do not have to compute the `PageScale`

constant, since it does that for you. Plus, you may use double precision.

The `GridView`

class provides the methods to pass from a "pixel" geometry to some other "logical units" geometry. So, when you have to draw a rectangle with a width of 4.5 cm and a height of 3.0 cm to a 400x500 panel, all you have to do is:

- Initialize
`GridView`

with desired width of panel in logical units (if you want to display the 4.5 wide rectangle, a `logicalW`

parameter of 10.0 seems perfect)
- Pass your panel size in pixel

In the case above the `GridView`

will be constructed with:

GridView gv = new GridView(10.0,400,500);

Now, if you want to know how many pixels in this panel, a rectangle of 4.5 cm wide is, or if you want to know where the point (1.2cm,2.4cm) is, you may use:

int pixelWidth = gv.getPhysicalWidth(4.5);
int pX = gv.getPhysicalX(1.2);
int pY = gv.getPhysicalY(2.4);

## Drawing spheres

When the `GridView`

is initialized, the panel has now 2D cartesian axis (in the downloadable example, the length of them is 5.0 and the coordinates are displayed on top right).

An object of the class `Sphere`

can be initialized like below:

Sphere s1 = new Sphere(gv,r,this.centerX,this.centerY);
s1.SphereColor=this.txtColor.BackColor;

where

`gv`

is the initialized `GridView`

object
`r`

is the sphere radius
`centerX`

is the X coordinate of the center (in logical units)
`centerY`

is the Y coordinate of the center (in logical units)

The algorithm for drawing the sphere is very simple, and it is based on drawing arcs in a rectangle whose dimensions shrink. The algorithm draws the arcs first from right to left, then from top to bottom.

private void drawArcs(Graphics g, Pen color, Rectangle r)
{
int x1=r.Left+r.Width/2;
int y1=r.Top;
int x2=x1;
int y2=r.Top+r.Height;
int x3=r.Left;
int y3=r.Top+r.Height/2;
int x4=r.Left+r.Width;
int y4=y3;
g.DrawLine(color,x1,y1,x2,y2);
g.DrawLine(color,x3,y3,x4,y4);
for (int j=r.Width; j>0; j-=10)
{
int left = r.Left+(r.Width-j)/2;
Rectangle rc = new Rectangle(left,r.Top,j,r.Height);
g.DrawArc(color,rc,0.0F,180.0F);
g.DrawArc(color,rc,180.0F,360.0F);
}
for (int j=r.Height; j>0; j-=10)
{
int top = r.Top+(r.Height-j)/2;
Rectangle rc = new Rectangle(r.Left,top,r.Width,j);
g.DrawArc(color,rc,270.0F,450.0F);
g.DrawArc(color,rc,90.0F,270.0F);
}
}

Modifying the number of loops (j-=10) you can obtain finer wires. You can also modify the width of the `Pen`

in order to get different results.

## Point of Interest

When you draw something on a `Control`

, on a `Panel`

in this case, if you cover the control with another control (or another window), the contents just drawn disappears. For this reason, every new sphere that you draw is collected into an `ArrayList`

collection. Whenever the `Paint`

event is raised, I call the `paint`

method for every shape in the collection.