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:
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:
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;