How To Write Wings3D Plugins
(for beginners)


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.


This page was last revised on April 1, 2004
Copyright © 2004 Anthony D'Agostino
All rights reserved.