Appendix 5. An introduction to SDL
(last modified: 11/4/99 - fsh)

The Scene Description Language (SDL) is a very simple language for describing geometric objects and light sources in a clear human readable fashion. It is understood by the read() method of the Scene class. (See the definition of the Scene class in Appendix 3.) We first look briefly at the basics of the Scene class, and then describe the SDL language, and discuss how it is used.

To read a scene file, say myScene.dat, written in SDL, first define a global Scene object, say, scn, and call the read() method for it, giving it the name of some SDL file to process:

Scene scn; // create a Scene object
.
.
scn.read("myScene.dat"); // read the SDL file, make the scene

The file myScene.dat is read and interpreted, and a list of objects is built. A list of light sources is also built. These lists are available through the fields scn.obj and scn.light, respectively. These lists are used by the drawOpenGL() method described in Chapter 5 to render the scene using OpenGL's facilities, or alternatively by shade() described in Chapter 14 to render the scene using ray tracing.

The Scene Class:
An object of the Scene class has several fields that describe the nature of a scene. The principal ingredient is a list of the geometric shapes that reside in the scene. The field obj is a pointer to the first shape on the list. To draw all of the objects on the list simply move through the list, telling each object to draw itself:

for(GeomObj* p = scn.obj; p != NULL; p = p->next)

p->drawOpenGL();

Objects on the list are of one type of Shape or another, (such as Sphere, Cube, Icosahedron, etc.) and each type of Shape knows how to draw itself. Polymorphism is used here: all Shape types are derived from the base type GeomObj, (short for ‘Geometric Object’), so any Shape type can reside on a list of pointers to GeomObj.

Other fields of the Scene class:
Light* light; // the light source list
GeomObj* obj; // the object list
Color3 background; // the background color
Color3 ambient; // the global ambient color

In addition, there are three fields used for ray tracing (see Chapter 14): maxRecursionDepth, minShinyness, and minTransparency.

5.1 Syntax of SDL
SDL is case sensitive but free form: multiple white-space characters (space, tab, newline, form feed, etc.) are equivalent to a single space. Comments begin with a '!' and continue to the end of the line.

Keywords in SDL are used to specify different affine transformations, geometric objects, light sources, and attributes of the scene such as the background color.

Creating Geometric Objects.
An object is created and placed on the object list simply by stating its type. For instance
cube adds a cube object to the object list, and
sphere adds a sphere object.
Other geometric objects include (see Scene.h for a complete list):
torus, plane, square, cylinder, cone, tetrahedron, octahedron, dodecahedron, icosahedron,
buckyball, diamond, teapot.

When any of these object types is specified in the file, the corresponding object is added to the (end of the) object list.

There are additional geometric object types that require a parameter, which is either a floating point value or a file name. The parameter is placed directly after the name of the object, as in:

taperedCylinder .312 !make a tapered cylinder with small radius .312
mesh pawn.3vn !make a mesh

The first example creates a tapered cylinder object that uses the parameter to define its exact shape; the second creates a mesh object whose vertex and face lists are described in the file pawn.3vn.

Managing Affine Transformations.
An affine transformation is stored with each object as it is created. (The inverse of this transformation is also stored: It is used when ray tracing.) The specific transformation that is installed with the object is the current transformation (CT) in effect at that moment. Various keywords in the SDL file alter the CT.

identityAffine

places the identity transformation (given by a unit 4 by 4 matrix) in the CT. The CT is initially the identity transformation when read() begins to interpret an SDL file.

SDL uses the words scale, rotate, and translate, each followed by suitable parameters, to alter the CT, in a manner very similar to how OpenGL uses glScalef(), glRotatef(), and glTranslatef() to alter the modelview or projection matrices. Specifically, each word postmultiplies the CT by the corresponding transformation, and places the product back into the CT, as in:

CT = CT * Trans

where Trans is the 4 by 4 matrix that represents the new transformation.

The three verbs are:

1. scale takes three floating point parameters which are the scale factors in the x-, y-, and z-directions, respectively.

2. rotate takes four parameters: the angle (in degrees) of the rotation, and the x-, y-, and z- components of the axis about which the rotation is to be made. (Positive values of ang produce CCW rotations about the u-axis, as seen looking from point u towards the origin.)

3.translate takes three parameters: the x-, y-, and z- components of the translation vector through which the translation is to be made.

For example, the commands
scale 2 1.3 –5.33
translate 4 -5 6
rotate 45 0 1 0

create the matrices Sc, Tr, and Rot given by

and each command postmultiplies the CT by its matrix and places the result back into the CT.

Stack of Affine Transformations.
The CT is actually the top matrix in a stack of matrices. The words push and pop manipulate this stack:

Managing Material Properties.
There is also a record of material properties, called the current materials (CM), that is installed in each object as it is created. This record consists of: Changes to the CM are managed by the following SDL words, each followed by one or more float’s, as suggested below:
ambient <r> <g> <b>
diffuse <r> <g> <b>
specular <r> <g> <b>
emissive <r> <g> <b>
specularExponent <value>
specularFraction <value>
surfaceRoughness <value>
speedOfLight <value>
transparency <value>
reflectivity <value>
textureType <value>
parameters <value> <value> <value> …

The parameters keyword is followed by the number of parameters being specified and the list of parameter values. For example, to place the three values 4.5, 6, and -12 in the params[ ] array of subsequently defined objects, you would use:

parameters 3 4.5 6 -12

The CM initially contains the default values:
ambient = ( 0.1, 0.1, 0.1)
diffuse = (0.8, 0.8, 0.8)
specular = (0, 0, 0)
emissive = (0, 0, 0)
specularExponent 1
specularFraction 0
surfaceRoughness 1.0
speedOfLight = 1
transparency = 0
reflectivity = 0
textureType 0

The word defaultMaterials can be used to return the CM to these default values.

Example SDL file:
! myScene1.dat - f.s.hill
! has several simple glowing objects
light 0 10 0 1 1 1 ! white light at (0,10,0)
background 0 0 .5
ambient .2 .2 .2
diffuse .8 .7 .6
emissive .8 0 0 ! objects emit red
cube ! put a generic cube at the origin
emissive 0 1 0
! put a glowing ellpsoid at (2, 0, 0)
push translate 2 0 0 rotate 45 0 0 1 scale .5 .5 2 sphere pop
push translate -2 0 0 cone pop !and a cone at (-2, 0 0)

Other Key Words:
light sources:
light <x> <y> <z> <r> <g> <b> !place a light at (x,y,z) having color (r,g,b)

Specifying global Scene Attributes:
globalAmbient <r> <g> <b> !give the global ambient source the color (r,g,b)
minReflectivity <value>
minTransparency <value>
maxRecursionDepth <value>
background <r> <g> <b>

Boolean objects:
union
intersection
difference

Each creates a boolean object of its specified type and places it on the object list. Each must be followed in the SDL file by two objects (each of which may be a geometric or a boolean object), which are placed as the left and right children of this boolean object.

Example:
light 1 2 3 0.2 0.3 0.4
intersection
cube !left child of intersection
union ! right child of intersection
diffuse .2 .5 .7 tetrahedron !left child of union
rotate 180 3 4 5 buckyball !right child of union
difference !another boolean
tetrahedron intersection
plane union
torus dodecahedron

Defining a pixmap to be used for texturing:
makePixmap <value> <fname>

5.2. Macros in SDL.
As a convenience, the key word def allows you to combine any number of SDL commands into a macro, and give them a single name. The SDL commands that form the body of the macro are enclosed in { } braces. For example:

def red {ambient 1 0 0 diffuse 1 0 0 }

associates the macro named red with the words shown. If later in the SDL file the command

use red

is encountered, SDL effectively places the phrase ambient 1 0 0 diffuse 1 0 0 at that point as if you had typed it there in the file. Macros can save typing and allow reuse of small definitions. For instance, an "L"-shaped stack of four cubes can be defined as:

def Lstack {push cube translate 2 0 0 cube
translate -2 2 0 cube
translate 0 2 0 cube pop}

This macro can be used later to place several different shaped L’s in the scene:

use Lstack
push translate 3 2 4 scale .5 .5 .5 use Lstack pop
push translate –3 –2 –4 scale .5 .5 .5 use Lstack pop

5.3. Extending SDL.
It is straightforward to add keywords to SDL We describe how to do this through two examples.

Example 1. Adding an attribute to scenes. Suppose you wish to have a new field, fogThickness, to the Scene class that describes the thickness of fog in the scene. To control the value to be placed in this field, you add the keyword fogginess to the SDL language. The syntax:

fogginess 0.5

will change the value of fogThickness to 0.5. To accomplish this, make the following changes.

Changes made in the file scene.h:
1). Add the field float fogThickness to the Scene class.
2). Add the item FOGTHICKNESS anywhere in the TokenType enumeration list.
3). In function whichtoken() add the line:

if (temp == "fogginess") return (FOGTHICKNESS);

Changes made in the file scene.cpp:
1). In the function getObject() add the line:

case FOGTHICKNESS: fogThickness = getFloat(); break;

The issue of how to use this new field is, of course, up to the programmer. For instance, while developing a raytracer the programmer adds some code to the Scene::shade() method such as: if(fogThickness > 0) do something..;

Example 2: Define a new type of object. Suppose you want to add a pie slice to the collection of possible objects appearing in scenes. This will be a portion of a thin circular disc lying in the xy-plane. The slice starts at angle 0 (directed along the x-axis) and continues CCW (as seen looking from (0,0,1) towards the origin) to angle sweep, measured in degrees. Thus if sweep is 180 the pie slice is half a pie in the positive y-quadrant, and if sweep is 360 the pie slice is a complete pie. We extend SDL so that the keyword pieSlice followed by a parameter for the sweep angle is recognized, as in

pieSlice 90

Changes made in the files shapes.h and shapes.cpp:
1). the PieSlice class is defined, with a field to hold the sweep angle:

class PieSlice : public Shape{
public:
float sweep;
etc.
};

The appropriate methods such as drawOpenGL() are coded for this class.

Changes made in the file scene.h:
1).Add the item PIESLICE anywhere in the TokenType enumeration list.
2). Add, in the function whichtoken(), the line:

if (temp == "pieSlice") return (PIESLICE);

Changes made in the file scene.cpp:
1). In the function getObject() add the line:
case PIESLICE:

newShape = new PieSlice;

((PieSlice*)newShape)->angle = getFloat(); break;