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