QuickModel File Format Reference

Note:

This text has been taken from the appendix of the SGI "Using QuickModel" manual, document number 007-9031-010, which is copyright 1989 by Silicon Graphics, Inc. The appendix was written by Martin Tuori at Alias Research. It is reproduced here without permission but in the hopes of being useful should anyone still be using the old QuickModel (.qm) 3D format.

"Notes" appearing in shaded boxes like this one have been added by me to clarify the original documentation.

The file format used by QuickModel to store 3-D scenes is a simple text format, intended to provide basic scene description capabilities.

Throughout the format, 3-D values for geometry and transformations are given in the order x, y, and z. Two dimensional values in surface parameter space are given in the order u and v. Color values are given in the order red, green, and blue. Decimal points are optional in integer values.

Geometric Primitives

Each component of a scene is called a model. You may want to think of these as objects. Here is a simple example - a sphere of default size, position, orientation, and surface characteristics:

model
{
    sphere();
}
      

The default sphere is of radius 0.5. Other primitive model types and their defaults are:

  • cone(), main axis is the y-axis, base at z=-0.5, point at z=0.5, radius at base = 0.5
  • cube(), length of each size = 1.0, center at 0, 0, 0
  • cylinder(), main axis is the y-axis, radius = 0.5, bottom at z=-0.5
  • light(), color is always white, located at the origin

These primitives currently take no parameters, although the parenthesis are included for consistency. There is no provision for object hierarchy, although temporary groups are supported in QuickModel (see "Transformations" (below) for information on group transforms).

Spline Curves and Surfaces

The QuickModel format supports cardinal splines for curves and surfaces. Here is an example of an open curve, containing four points:

model
{
    surface( 4, 1 );
    {
        CV( 3.1, 0, 3.0, 1, 0 );
        CV( 3.8, 0, 1.2, 0, 0 );
        CV( 3.0, 0, -0.2, 0, 0 );
        CV( 3.6, 0, -2.0, 1, 0 );
    }
}
      

A curve is represented as a one-dimensional surface; hence the parameters ( 4, 1 ). The control vertices (CV's) contain position information (the first three parameters) and multiplicity information (the last two parameters). To draw the spline curve out to the end points, it is necessary to treat the first and last CV's as if they were doubled.

The fourth parameter describes this multiplicity in the u direction, alone the curve. In this example, the first and last CV's of the curve have multiplicity of 1, meaning they are doubled. The other CV's have multiplicity of 0, meaning they are not doubled.

Multiplicity is also used to draw straight lines, rather than curved, between points. Try drawing a curve, picking one CV in the middle of the curve, and applying the Shape -> straight function. Save the model to a file and look at the multiplicity of the point that you straightened.

A closed curve is created by dropping the multiplicity of the end-points, and repeating 3 CV's, as in the following example of a closed, three-point curve:

model
{
    surface( 6, 1 );
    {
        CV( -3.9, 0, 2.9, 0, 0 );
        CV( -3.5, 0, -0.0, 0, 0 );
        CV( -3.9, 0, -2.9, 0, 0 );
        CV( -3.9, 0, 2.9, 0, 0 );
        CV( -3.5, 0, -0.0, 0, 0 );
        CV( -3.9, 0, -2.9, 0, 0 );
    }
}
      

Here is an example of an open, two-dimensional surface. It is a four-by-four patch:

model
{
    surface( 4, 4 );
    {
        CV( 1.0, -0.2, 3.1, 1, 1 );
        CV( 1.7, -0.2, 1.3, 0, 1 );
        CV( 0.9, -0.2, -0.1, 0, 1 );
        CV( 1.5, -0.2, -1.9, 1, 1 );

        CV( 1.3, -0.2, 3.1, 1, 0 );
        CV( 2.0, -0.2, 1.3, 0, 0 );
        CV( 1.3, -0.2, -0.1, 0, 0 );
        CV( 1.9, -0.2, -1.9, 1, 0 );

        CV( 1.7, -0.3, 3.0, 1, 0 );
        CV( 2.4, -0.3, 1.3, 0, 0 );
        CV( 1.6, -0.3, -0.1, 0, 0 );
        CV( 2.3, -0.3, -1.9, 1, 0 );

        CV( 2.1, -0.4, 3.0, 1, 1 );
        CV( 2.7, -0.4, 1.3, 0, 1 );
        CV( 2.0, -0.4, -0.1, 0, 1 );
        CV( 2.6, -0.4, -2.0, 1, 1 );
    }
}
      

The fifth parameter of each CV is its multiplicity in the v direction. CV's at the corners of a surface patch have multiplicities of 1 and 1, and those along the edges have multiplicities of 1 and 0, or 0 and 1.

If you want a CV in the middle of a surface to have multiplicity of 1, you must do the same to all CV's in the same row or column. This is necessitated by the way geometry is handled internally - the CV's with multiplicity of 1 are duplicated when fed to the geometry pipeline, and a consistent CV count in u or v must be maintained. For simplicity, all surface patches created using the Surface -> patch function have the same number of CV's in the u and v directions.

Surfaces created using the Surface -> revolve function have as many CV's in the u direction as the starting curve but always have six CV's in the v direction (nine, counting the CV's used to close the loop). Surfaces created using the Surface -> extrude function have just two CV's in the v direction.

For generality, however, QuickModel can read surface patches from other sources that have different numbers of CV's in u and v.

Below is an example of a surface that is closed in one direction. It was created by resolving a curve of three points. As in a closed curve, repetition is used to create a smooth closure; but in this example the three-point curve is repeated, rather than a single point.

model
{
    surface( 3, 9 );
    {
        CV( -2.9, 0, 1.4, 1, 0 );
        CV( -2.9, 0, -1.2, 0, 0 );
        CV( -3.0, 0, -3.0, 1, 0 );

        CV( -1.4, -2.5, 1.4, 1, 0 );
        CV( -1.4, -2.5, -1.2, 0, 0 );
        CV( -1.5, -2.5, -3.0, 1, 0 );

        CV( 1.4, -2.5, 1.4, 1, 0 );
        CV( 1.4, -2.5, -1.2, 0, 0 );
        CV( 1.5, -2.5, -3.0, 1, 0 );

        CV( 2.9, 0, 1.4, 1, 0 );
        CV( 2.9, 0, -1.2, 0, 0 );
        CV( 3.0, 0, -3.0, 1, 0 );

        CV( 1.4, 2.5, 1.4, 1, 0 );
        CV( 1.4, 2.5, -1.2, 0, 0 );
        CV( 1.5, 2.5, -3.0, 1, 0 );

        CV( -1.4, 2.5, 1.4, 1, 0 );
        CV( -1.4, 2.5, -1.2, 0, 0 );
        CV( -1.5, 2.5, -3.0, 1, 0 );

        CV( -2.9, 0, 1.4, 1, 0 );
        CV( -2.9, 0, -1.2, 0, 0 );
        CV( -3.0, 0, -3.0, 1, 0 );

        CV( -1.4, -2.5, 1.4, 1, 0 );
        CV( -1.4, -2.5, -1.2, 0, 0 );
        CV( -1.5, -2.5, -3.0, 1, 0 );

        CV( 1.4, -2.5, 1.4, 1, 0 );
        CV( 1.4, -2.5, -1.2, 0, 0 );
        CV( 1.5, -2.5, -3.0, 1, 0 );

    }
}
      

Finally, here is an example of a surface that is closed in both directions. It is a doughnut, created by revolving a closed curve of three points.

model
{
    surface( 6, 9 );
    {
        CV( -3.5, 0, 1.3, 0, 0 );
        CV( -2.4, 0, -0.6, 0, 0 );
        CV( -4.8, 0, -0.7, 0, 0 );
        CV( -3.5, 0, 1.3, 0, 0 );
        CV( -2.4, 0, -0.6, 0, 0 );
        CV( -4.8, 0, -0.7, 0, 0 );

        CV( -1.7, -3.0, 1.3, 0, 0 );
        CV( -1.2, -2.1, -0.6, 0, 0 );
        CV( -2.4, -4.1, -0.7, 0, 0 );
        CV( -1.7, -3.0, 1.3, 0, 0 );
        CV( -1.2, -2.1, -0.6, 0, 0 );
        CV( -2.4, -4.1, -0.7, 0, 0 );

        CV( 1.7, -3.0, 1.3, 0, 0 );
        CV( 1.2, -2.1, -0.6, 0, 0 );
        CV( 2.4, -4.1, -0.7, 0, 0 );
        CV( 1.7, -3.0, 1.3, 0, 0 );
        CV( 1.2, -2.1, -0.6, 0, 0 );
        CV( 2.4, -4.1, -0.7, 0, 0 );

        CV( 3.5, 0, 1.3, 0, 0 );
        CV( 2.4, -1.3, -0.6, 0, 0 );
        CV( 4.8, 0, -0.7, 0, 0 );
        CV( 3.5, 0, 1.3, 0, 0 );
        CV( 2.4, -1.3, -0.6, 0, 0 );
        CV( 4.8, 0, -0.7, 0, 0 );

        CV( 1.7, 3.0, 1.3, 0, 0 );
        CV( 1.2, 2.1, -0.6, 0, 0 );
        CV( 2.4, 4.1, -0.7, 0, 0 );
        CV( 1.7, 3.0, 1.3, 0, 0 );
        CV( 1.2, 2.1, -0.6, 0, 0 );
        CV( 2.4, 4.1, -0.7, 0, 0 );

        CV( -1.7, 3.0, 1.3, 0, 0 );
        CV( -1.2, 2.1, -0.6, 0, 0 );
        CV( -2.4, 4.1, -0.7, 0, 0 );
        CV( -1.7, 3.0, 1.3, 0, 0 );
        CV( -1.2, 2.1, -0.6, 0, 0 );
        CV( -2.4, 4.1, -0.7, 0, 0 );

        CV( -3.5, 0, 1.3, 0, 0 );
        CV( -2.4, 0, -0.6, 0, 0 );
        CV( -4.8, 0, -0.7, 0, 0 );
        CV( -3.5, 0, 1.3, 0, 0 );
        CV( -2.4, 0, -0.6, 0, 0 );
        CV( -4.8, 0, -0.7, 0, 0 );

        CV( -1.7, -3.0, 1.3, 0, 0 );
        CV( -1.2, -2.1, -0.6, 0, 0 );
        CV( -2.4, -4.1, -0.7, 0, 0 );
        CV( -1.7, -3.0, 1.3, 0, 0 );
        CV( -1.2, -2.1, -0.6, 0, 0 );
        CV( -2.4, -4.1, -0.7, 0, 0 );

        CV( 1.7, -3.0, 1.3, 0, 0 );
        CV( 1.2, -2.1, -0.6, 0, 0 );
        CV( 2.4, -4.1, -0.7, 0, 0 );
        CV( 1.7, -3.0, 1.3, 0, 0 );
        CV( 1.2, -2.1, -0.6, 0, 0 );
        CV( 2.4, -4.1, -0.7, 0, 0 );

    }
}
      

Transformations

The transformation of a model is given by the rotate(), scale(), and translate() functions. Here is an example of a cube that has undergone some transforms:

model
{
    rotate( 45, 30, 0 );
    scale( 2.0, 1, 5.5 );
    translate( -0.6, 0, -4.0 );
    cube();
}
      

Rotation is given in degrees, around the x, y, or z axis, relative to the center of the model. A scale of two doubles the size of the model. A translation of one corresponds to one grid unit in QuickModel.

An additional transformation matrix may also be specified. QuickModel uses this tm() to represent the transformations that a model has undergone as part of temporary groups. For data translation from other 3-D sources, the tm() provides an alternate to rotate(), scale(), and translate() as a means of representing a model's transformations. Here is the previous example, shown with the addition of the default tm().

model
{
    tm( ( 1, 0, 0, 0 ),
        ( 0, 1, 0, 0 ),
        ( 0, 0, 1, 0 ),
        ( 0, 0, 0, 1 ) );
    rotate( 45, 30, 0 );
    scale( 2.0, 1, 5.5 );
    translate( -0.6, 0, -4.0 );
    cube();
}
      

The transformations are applied in this order: scale, rotate, translate, and then the tm(). The tm() is represented as in SGI's GL.

Note:

The rotate(), scale(), tm(), and translate() transformations are applied using OpenGL function calls in the following order:

glMultMatrixf(tm);
glTranslatef(tx, ty, tz);
glRotatef(rx, 1.0, 0.0, 0.0);
glRotatef(ry, 0.0, 1.0, 0.0);
glRotatef(rz, 0.0, 0.0, 1.0);
glScalef(sx, sy, sz);
	

where tm is the matrix defined by the tm() function, tx, ty, and tz are the values passed to the translate() function, rx, ry, and rz are the values passed to the rotate() function, and sx, sy, and sz are the values passed to the scale() function.

Surface Characteristics

The functions color(), diffusion(), and specularity() describe the surface appearance of a model for rendering. If a model has no explicit surface characteristics, it is given the defaults color(0.3, 0.6, 1.0), diffusion(0.8), and specularity(0.6).

QuickModel produces diffusion and specularity values in the range 0.0 to 1.0, but higher values are permitted.

Note:

There is at least one QuickModel rendering library that uses the diffusion() value to specify alpha, with the default alpha value being 1.0 (opaque).

Summary

The QuickModel file format was designed for ease of use. It is human-readable, to facilitate experimentation and data translation to and from other data sources.