Introduction
This tutorial will attempt to teach you how to write your own
primitive-creating plugins for Wings. First, an overview
of the fundamental principles of what primitives are and how they
are stored will be presented. Then, an explanation of how to apply
these basic concepts in terms of Erlang and within the confines of
the plugin api.
Coordinate System
When writing a plugin for a particular modeling application, it is
important to understand the orientation (or handedness) of the
coordinate system. Knowing this from the beginning will prevent
problems such as faces pointing in the wrong direction, having to
manually rotate your model so it lines up with the floor, etc.
For the record, Wings uses a right-handed coordinate system: the
positive x, y, and z axes point to the right, up, and toward you,
respectively. Upon startup (or when you reset the view) the
camera's location is at position {1,1,1}.
This sounds simple, but it is easy to get confused. Do you know
which way the axes are pointing when you are in top view (i.e.,
viewing along the +y axis)? The answer: +x points to the right, and
-z points to the top of the screen.
Geometric Primitive
Primitives are planes, cubes, spheres, cylinders, etc. They can
also be called meshes or objects. Primitives with flat surfaces
(cubes, gears, icosahedrons) can be represented with 100% accuracy.
But since Wings is a polygon modeler, primitives with curved
surfaces (spheres, torus knots, teapots) must be represented
parametrically. Therefore, a bivariate function is employed, and as
the two parameters, Ures and Vres, are increased,
the polygonal approximation approaches the true shape of the
primitive.
There are two main pieces of information needed in order to create
a mesh: a vertices list and a faces list. In other applications,
and possibly in future Wings versions, you may also need extra
information such as the object's name, the uv coordinates, vertex
normals, vertex colors, etc.
Vertex
A vertex is an x,y,z triplet that specifies a location or point in
a particular coordinate system. For example, the lower-left,
front-vertex of a cube is usually located at {-1.0, -1.0, +1.0}.
Face
A face, or polygon, is a finite, flat two-dimensional surface
consisting of an arbitrary number of vertices. A minimum of three
vertices are required to define a face (resulting in a triangle).
There really is no maximum; a face with 3,000 vertices arranged in
a circle will produce a very smooth disk. Faces may be convex
(i.e., shaped like a hexagon) or concave (i.e., shaped like a the
letter U).
A face with two vertices is a one-dimensional edge. A face with one
vertex is just a zero-dimensional point. Some applications allow
such faces and can they can prove to be very useful to render
hair/fur and for particle effects. Wings, however, has a minimum of
three verts per face.
Mesh
A mesh is simply a group of polygons arranged in a way that forms
some sort of 3D object, such as a primitive. There are many ways to
represent meshes; some of the most common are described in the data
structures below:
-
raw format: A list of faces. Each face is represented by
a list of the vertices that make up that face. This is very
simple, but uses lots of storage space due to the large number of
duplicate vertices that are present.
-
indexed format: This is nearly the same as raw format.
The difference is that the verts are put into a separate list and
all duplicates are removed. The list of the vertices that make up
each face is replaced with a list of indices. This is much more
compact.
-
winged-edge format: A list of edges. This is a completely
different structure and is much more useful during modeling than
for creating primitives or storing meshes.
Vertex Order
Another concept that you should know concerning the list of faces
in the above formats is vertex order. In Wings, vertex
order is couter-clockwise. This means that when you define a face,
you must list the vertices (or indices) in couter-clockwise order.
If listed in reverse, the face normal will point in the wrong
direction. This can be corrected in Wings through the
invert command, but should be avoided to begin with. It is
extremely important (and necessary) to use a consistent order for
all faces.
Erlang: A whole 'nother language
Erlang is a very interesting programming language. It is different
and may appear to be strange and difficult when compared to C or
Python. In order for you to learn Erlang quickly and easily, you
must do two things: forget any other language that you know, and
change your way of thinking. These two rules are especially
important when you attempt to write your first function.
Lists
I won't explain every little detail about Erlang. (I don't fully
understand all of it myself!) However, I will explain the
one feature that is crucial to writing plugins:
lists. For simple plugins, all you need to know is how to
dynamically generate a list. That's it. The rest of the plugin code
is just a copy & paste operation with a few module/plugin name
changes.
For more advanced plugins, you may need to know how to traverse
lists, access certain elements, create a dictionary from a list,
and so on. My meshtools module has lots of functions that
do these types of operations so feel free to use it. Also, check
out Erlang's lists module; and read up on list
comprehensions, which can take the place of nested for
loops.
Erlang expressions
The following table shows how some of these fundamental concepts
are written as Erlang expressions.
a vertex is a tuple of 3 floats
|
{-1.0, 1.0, 0.0}
|
a triangle is a list of 3 vertices
(a list of 3 tuples)
|
[{-1.0, 1.0, 0.0},
{0.0, 1.0, 0.0},
{1.0, 0.0, 0.0}]
|
rawtriangles is a list of triangles
(a list of lists of 3 tuples)
|
[[{-1.0, 1.0, 0.0},
{0.0, 1.0, 0.0},
{1.0, 0.0, 0.0}],
[{-1.0, 0.0, -1.0},
{0.0, 0.5, -1.0},
{0.5, 0.0, 0.0}]]
|
a face is a list of indices
|
[0, 1, 4]
|
faces is a list of faces
(a list of lists of indices)
|
[[0, 1, 4], [2, 5, 0], [1, 0, 7]]
|
Erlang Functions
The following function from my geodome plugin shows how to
represent a tetrahedron as an Erlang function. It "generates" the
verts and faces list, then simply returns both of them neatly
packed up in a tuple.
tetrahedron() ->
A =
math:sqrt(1.0/8.0),
B =
math:sqrt(1.0/4.0),
C = 0.0,
Verts = [{C, A, B}, {C, A,
-B}, {-B, -A, C}, {B, -A, C}],
Faces = [[0, 1, 2], [0, 2,
3], [0, 3, 1], [1, 3, 2]],
{Verts, Faces}.
|
Python to Erlang
Let's see how simple it is to generate a vertices list and
a faces list for a simple plane object with an arbitrary
level of resolution. This type of primitive should be parallel to
the floor, so let's arrange the vertices on the xz-plane;
all y-coordinates, therefore, must be set to zero. The
following table compares Python code to Erlang code:
Python
|
Erlang
|
>>>
range(6)
[0, 1, 2, 3, 4, 5]
|
1>
lists:seq(0,5).
[0,1,2,3,4,5]
|
>>> u
= 3
>>> v = 2
>>> verts = [(i,0,j)
\
... for i in range(u) \
... for j in range(v)]
>>> verts
[(0, 0, 0),
(0, 0, 1),
(1, 0, 0),
(1, 0, 1),
(2, 0, 0),
(2, 0, 1)]
|
2> U =
3,
2> V = 2,
2> Verts = [{I,0,J} ||
2> I <- lists:seq(0,
U-1),
2> J <- lists:seq(0,
V-1)],
2> Verts.
[{0,0,0},
{0,0,1},
{1,0,0},
{1,0,1},
{2,0,0},
{2,0,1}]
|
>>>
faces = [[i*v+j, \
... i*v+j+1, \
... (i+1)*v+j+1, \
... (i+1)*v+j] \
... for i in range(u-1) \
... for j in range (v-1)]
>>> faces
[[0, 1, 3, 2], [2, 3, 5,
4]]
|
3> Faces =
[[I*V+J,
3> I*V+J+1,
3> (I+1)*V+J+1,
3> (I+1)*V+J] ||
3> I <- lists:seq(0,
U-2),
3> J <- lists:seq(0,
V-2)],
3> Faces.
[[0,1,3,2],[2,3,5,4]]
|
When it comes to list comprehensions, Python and Erlang are nearly
identical. The only thing to be aware of is one-off errors. Compare
Python's range() to Erlang's lists:seq(). Start
up the Erlang Shell and paste some of the above code (in blue) to
experiment for yourself.
Figure 1 illustrates how
the plane object would appear in the geometry window. The vertex
indices are denoted in light blue; the face indices in light
red. The concept of counter-clockwise vertex order should now be
quite apparent. Note: The code fragment above produces an
invalid mesh and will crash in current versions of Wings. The
reason is that only one edge is shared by two faces, the other 6
edges are open.
Winged Edge Data Structure
Keep in mind that Wings will attempt to build a
winged edge data structure from your verts and
faces lists. This does not always work for every type of
input mesh. The following conditions should be avoided from the
start because they will surely make your plugin crash:
- duplicate vertices
- each edge is not shared by exactly two faces
- inconsistent vertex-order
- missing faces or holes
- less than 3 vertices in any face
Another thing to watch out for is floating-point round-off error. A
vertex might print out as {1.0, 0.0, 0.1}
but might actually be represented internally as {1.0, 0.0, 0.099999999}. Keep this in mind when
something goes wrong and you can't figure out what it is.
Links
You should get a text editor with color syntax-highlighting to make
Erlang easier to read. I highly recommend the Thomson-Davis Editor; here's a screenshot. For more about
Erlang, read the tutorial by Sean Hinde. Click on the INFO link at the
top of this page and check out the source of my plugins. And
don't keep an eye out for my upcoming book: Inside Wings3D Plugins: A
Developer's Guide.
Conclusion
Well, this is not the end because there is still so much that
hasn't been covered. However, all of my plugins were written based
on the few techniques and concepts presented here. I have tried to
be as comprehensive as possible and this tutorial should give you a
great head start on your journey to plugin writing. For other
information about interactivity with the erlang shell, see my other
tutorial: Exploring Wings3D through
the Erlang Shell.
|