Drawing thicker lines for DirectX wireframe modeling

An annoying disability of DirectX is that wireframe lines cannot vary in thickness

An annoying disability of DirectX is that wireframe lines cannot vary in thickness. Imagine creating a 3D modeling program that renders your objects in wireframe mode. You quickly notice that the lines are very thin and thus not very presentable as a wirefame model. In OpenGL this was no problem. The line thickness could easily be changed using glLineWidth(w);

 

How then can one get around creating a thick lined wireframe in DirectX? One way is to use the Direct3D’s Line command as follows:

 

Line l = new Line(device);

l.GlLines = true;

l.Width = 3;

c = Color.Red;

l.Begin();

l.Draw(vecs, c);

l.End();

 

This works well for 2d line models. To create 3d models you’d first have to project the model from 3d space into 2d screen coordinates because the “Line” class only deals with 2d line art. This works fine and may be a good alternative solution for simple models. The only hitch is that this procedure has a very high preformance penalty. With many primitives and vertices it starts to chug and drag – a not very optimal way of doing 3d graphics.

 

Not to worry, there is a way.

What if we create a quadrilateral for every line that is drawn. The quad would look long and thin, thin enough to be a line segment. We would have six verteces for every line. Know that a quad is essentially made up of two triangles, therefore 6 vertices (3 per triangle.) Now a quad will be visible if viewed straight on. It won’t be visible if viewed along it’s plane. To solve this, one could create a quad for every plane, the x-plane, the y-plane, and the z-plane. This makes the lines of the model visible no matter how you look at it. Okay, now we end up with 3 x 6 = 18 vertices per line. Using device.SetStreamSource this does not slow you down.

 

Below is a snippet of code that creates this type of senario. Much of the code has other classes defined, like Polygon, CVector, etc. The object here is to merly show a way to create the 3 quads per line.

 

protected void CreateLineVertexBuffer()

{

if (colnverts == null)

colverts = new CustomVertex.PositionColored[Polygons.SidesCount * 6 * 3];

int vi = 0;

 

CVector[] vec = new CVector[4];

float thickness = 0.01f;

 

foreach (Polygon p in Polygons)

{

int c;

if (w.Selected)

c = Color.FromArgb(255, 255, 0, 0).ToArgb();

else

c = Color.FromArgb(0, 0, 0, 255).ToArgb();

 

CreateColoredQuad(ref vi, p.Points[0], p.Points[1], vec, c, thickness);

CreateColoredQuad(ref vi, p.Points[1], p.Points[2], vec, c, thickness);

if (p.Numpoints < 4)

CreateColoredQuad(ref vi, p.Points[2], p.Points[0], vec, c, thickness);

else

{

CreateColoredQuad(ref vi, p.Points[2], p.Points[3], vec, c, thickness);

CreateColoredQuad(ref vi, p.Points[3], p.Points[0], vec, c, thickness);

}

}

}

Polygons is some class representing polygons of either 3 sides or four sides.

CVector is some vector class which defines an array of 4 vec’s each that will have the coordinates for the quads (lines.)

Polygons.SidesCount are the number of sides to a polygon.

protected void CreateColoredQuad(ref int vi, CVector v1, CVector v2, CVector[] vec, int c, float thickness)

{

CVector upvec = new CVector(0, 0, 1);

vec[0] = v1;

vec[1] = v2;

CVector vdir = (v2 - v1).UnitVector();

CVector vdirperp = vdir ^ upvec;

vec[2] = v2 + vdirperp * thickness;

vec[3] = v1 + vdirperp * thickness;

 

colverts[vi++] = new CustomVertex.PositionColored(vec[0].ToVector3(), c);

colverts[vi++] = new CustomVertex.PositionColored(vec[1].ToVector3(), c);

colverts[vi++] = new CustomVertex.PositionColored(vec[2].ToVector3(), c);

 

colverts[vi++] = new CustomVertex.PositionColored(vec[2].ToVector3(), c);

colverts[vi++] = new CustomVertex.PositionColored(vec[3].ToVector3(), c);

colverts[vi++] = new CustomVertex.PositionColored(vec[0].ToVector3(), c);

 

vec[0] = v1;

vec[1] = v2;

 

upvec = new CVector(0, 1, 0);

vdir = (v2 - v1).UnitVector();

vdirperp = vdir ^ upvec;

vec[2] = v2 + vdirperp * thickness;

vec[3] = v1 + vdirperp * thickness;

 

colverts[vi++] = new CustomVertex.PositionColored(vec[0].ToVector3(), c);

colverts[vi++] = new CustomVertex.PositionColored(vec[1].ToVector3(), c);

colverts[vi++] = new CustomVertex.PositionColored(vec[2].ToVector3(), c);

 

colverts[vi++] = new CustomVertex.PositionColored(vec[2].ToVector3(), c);

colverts[vi++] = new CustomVertex.PositionColored(vec[3].ToVector3(), c);

colverts[vi++] = new CustomVertex.PositionColored(vec[0].ToVector3(), c);

 

upvec = new CVector(1, 0, 0);

vdir = (v2 - v1).UnitVector();

vdirperp = vdir ^ upvec;

vec[2] = v2 + vdirperp * thickness;

vec[3] = v1 + vdirperp * thickness;

 

colverts[vi++] = new CustomVertex.PositionColored(vec[0].ToVector3(), c);

colverts[vi++] = new CustomVertex.PositionColored(vec[1].ToVector3(), c);

colverts[vi++] = new CustomVertex.PositionColored(vec[2].ToVector3(), c);

 

colverts[vi++] = new CustomVertex.PositionColored(vec[2].ToVector3(), c);

colverts[vi++] = new CustomVertex.PositionColored(vec[3].ToVector3(), c);

colverts[vi++] = new CustomVertex.PositionColored(vec[0].ToVector3(), c);

}

 

Above creates three quads for each line. The upvec represents the plane’s normal direction.

“vdir” is the direction of the line.

“vdirperp” is the cross product of vdir and upvec (vdir ^ upvec).

The “ ^ ” is an operator that defines the cross product (code not shown.) If you’re dealing with 3d graphics, you would have your own Vector class. Using DX’s Vector3.Cross would be another way to find the cross product.

Finally, we render the wireframe. Be sure the primitive count is right.

 

 

protected void RenderLineColoredPolygons()

{

if (Polygons != null)

{

if (Polygons.Count > 0 && colverts != null)

{

colvb.SetData(colverts, 0, LockFlags.None);

 

device.VertexFormat = CustomVertex.PositionColored.Format;

device.SetStreamSource(0, colvb, 0);

 

device.DrawPrimitives(PrimitiveType.TriangleList, 0, Polygons.SidesCount * 2 * 3);

}

}

}

 

Conclusion

 

The resultant wireframe is quite satisfactory as long as the thickness stays within a certain range. The thicker the lines, the more it will look like a bunch of L-beams. The code can be adjusted to resemble a bunch of cross beams. Other than that it would not be noticible if the so-called “flanges” are thin enough.

 

by

Frank Neubecker