Computer Graphics Workshop '97 Problem Set 2

1/8/97

Problems This problem set is primarily designed to give you practice with reading the man pages and figuring out yourself how to call new methods and hook together fields.

Problem 1 - Modifying the scene

The starting point for this problem is the scene graph from problem 2 of problem set 1.

Look at the manual page for SoSFRotation, the data type of the rotation field of the SoTransform class. This field has the following setValue methods:

void setValue(float q0, float q1, float q2, float q3)
void setValue(float q[4])
void setValue(const SbVec3f &axis, float angle)

We will ignore the first two methods, which involve the use of quaternions to represent a rotation. The third method allows the specification of a right hand rotation about the axis starting at the origin by the number of radians specified by the angle argument. This means that if you hold out your right hand with the four fingers together and the thumb extended perpendicular to your hand, a positive rotation about your "thumb axis" goes in the direction your fingers curl.

Note that the first argument to this setValue method is a reference to an SbVec3f. This is a C++ construct which you do not have to understand or use in this course. All you have to know is that the first argument to this method must be of type SbVec3f; and there is only one way to create an SbVec3f, using the call to new-SbVec3f. This is true in practically all of the cases in the manual pages; all you have to do is get the data types of the arguments correct in calls to methods.

For example, to cause a transform node to represent a rotation about the X axis by 90 degrees (pi/2 radians), you would do the following:

(define M_PI 3.14159265358979323846)
(-> (-> my-transform 'rotation) 'setValue (new-SbVec3f 1 0 0)
                                          (/ M_PI 2.0))
Experiment with the rotation fields of the nose and mouth. Turn the cone upside down. Make the cube stand diagonally on an edge, then on a corner. To do the latter problem you may want to use a TrackballManip to position the mouth in the appropriate position, then get the axis and angle out of the rotation field of this manipulator. The getValue method of the class SoSFRotation which allows this is

void getValue(SbVec3f &axis, float &angle)

In order to use this method, you must define two new variables, one an SbVec3f and one a floating point number, and pass them in as arguments to the rotation field's getValue method. Hint: to define a floating point number, just evaluate (define ang 0.0).

Note that the getValue method of SoSFRotation modifies, or mutates, the axis and angle passed in. Most of the time you will not have to use functions which do this, so if you don't understand how this works, don't worry about it.

Problem 2 - Using draggers to modify the scene

Another useful facility of Inventor-style fields is that they can be connected together; that is, if one field's value is changed, it can cause another to change as well automatically. Let's hook up a dragger to see how this works.

An SoTranslate1Dragger is an object which translates along the X axis in response to mouse clicks. Let's create one and see what it looks like.

(define viewer (new-SoXtExaminerViewer))
(-> viewer 'show)

(define root (new-SoSeparator))
(-> root 'ref)

(define drag (new-SoTranslate1Dragger))
(-> root 'addChild drag)

(-> viewer 'setSceneGraph root)
Now we see what the dragger looks like. Use the dolly wheel to back away from the dragger, click on the arrow icon (not the image of the dragger), and then click and drag on the dragger. Note that it moves only along its horizontal axis.

Let's use the dragger to modify the translation of a cone.

(define transform (new-SoTransform))
(-> root 'addChild transform)

;; move the cone above the dragger
(define transform2 (new-SoTransform))
(-> (-> transform2 'translation) 'setValue 0 2 0)
(-> root 'addChild transform2)

(define cone (new-SoCone))
(-> root 'addChild cone)

;; Connect the translation field of the transform node to the
;; translation field of the dragger node.

(-> (-> transform 'translation) 'connectFrom (-> drag 'translation))
Now when we move the dragger, the cone moves along with it.

An engine is an object which connects to one or more fields as inputs, performs some operation on their values, and forces those values to the fields which are its outputs. By using an engine, we can perform more specific manipulations than just moving the cone along with the dragger.

Let's disconnect the cone's translation from the dragger by disconnecting the translation field of the first transform node.

(-> (-> transform 'translation) 'disconnect)
Now we'll create an SoDecomposeVec3f engine. From looking at the manual page, we see that this type of engine takes one or more vectors as inputs and outputs three sets of one or more floating point values. We will use this engine to access the horizontal component of the dragger's translation and hook it up to the radius of the cone.
(define decomp (new-SoDecomposeVec3f))
(-> (-> decomp 'vector) 'connectFrom (-> drag 'translation))
(-> (-> cone 'bottomRadius) 'connectFrom (-> decomp 'x))
Now when we click and drag on the dragger, the radius of the cone changes.

NOTE how the system of reference counting works with engines: whenever an output of an engine is connected to some field, the reference count of the engine is incremented by 1. Whenever an output of an engine is disconnected, the reference count of the engine is decremented by 1. Whenever an engine's reference count is decremented to 0, the engine is deleted. To avoid accidents, you should probably ref each engine you create while you experiment.

By using the analogue of the decomposition engine, SoComposeVec3f, we see that we could have connected just the horizontal component of the dragger's translation to the translation field of the transform node, allowing us to implement the scene graph with fewer nodes:

(define root (new-SoSeparator))
(-> root 'ref)

(define drag (new-SoTranslate1Dragger))
(-> root 'addChild drag)
(-> (-> drag 'translation) 'setValue 0 -2 0)

(define cone-sep (new-SoSeparator))
(-> root 'addChild cone-sep)

(define transform (new-SoTransform))
(-> cone-sep 'addChild transform)

(define cone (new-SoCone))
(-> cone-sep 'addChild cone)

(define decomp (new-SoDecomposeVec3f))
(-> (-> decomp 'vector) 'connectFrom (-> drag 'translation))

(define comp (new-SoComposeVec3f))
(-> (-> comp 'x) 'connectFrom (-> decomp 'x))

(-> (-> transform 'translation) 'connectFrom (-> comp 'vector))
Note that the default values for all of the elements of the SoComposeVec3f engine are 0; so the vector it composes will have values (x,0,0) by default, where x is the horizontal translation of the dragger. We could fix the other two elements of the output vector by writing, for example,
(-> (-> comp 'y) 'setValue 2.0)
Now the distance between the dragger and cone changes.

Hook up at least one Translate1Dragger to your scene (for example, to control the radius of one of the eyes) to understand how this process works. You may find the following convenience functions useful:

; convenience constructor for decomposing a Vec3f field
(define (decomposeVec3f field)
  (let ((decomp (new-SoDecomposeVec3f)))
    (-> (-> decomp 'vector) 'connectFrom field)
    decomp))

; convenience constructor for composing a Vec3f
; #f argument means leave it unbound
(define (composeVec3f x y z)
  (let ((comp (new-SoComposeVec3f)))
    (if x (-> (-> comp 'x) 'connectFrom x))
    (if y (-> (-> comp 'y) 'connectFrom y))
    (if z (-> (-> comp 'z) 'connectFrom z))
    comp))
Problem 3 - Further modifications with draggers

Look at the manual page for SoRotateCylindricalDragger and SoRotateDiscDragger. These two types of draggers implement rotation about an axis (Y and Z, respectively). If you connect the rotation field from the dragger to the rotation field of the cone's transform in the above scene graph, the cone will rotate about the Y or Z axis along with the dragger.

We would like you to make the cone rotate about the X axis instead. You can use the SoDecomposeRotation engine to convert the rotation field of either dragger into an axis and angle, and the SoComposeRotation engine to recreate a rotation that can be hooked up to the rotation field of the transform node. However, the axis field in the SoDecomposeRotation engine will not be a constant vector; you can not just hook up the angle field and set the axis to be the x axis (1, 0, 0). Hint: use the SoDecomposeVec3f and SoComposeVec3f engines on the axis field to get the appropriate behavior of the rotating cone. (The wrong behavior has a discontinuity during the rotation; if the cone rotates smoothly about the X axis through 360 degrees, you've got the right answer.)

You may find the following convenience functions useful:

; convenience constructor for decomposing a Rotation field
(define (decomposeRotation field)
  (let ((decomp (new-SoDecomposeRotation)))
    (-> (-> decomp 'rotation) 'connectFrom field)
    decomp))

; convenience constructor for composing a Rotation
; #f argument means leave it unbound
(define (composeRotation axis angle)
  (let ((comp (new-SoComposeRotation)))
    (if axis (-> (-> comp 'axis) 'connectFrom axis))
    (if angle (-> (-> comp 'angle) 'connectFrom angle))
    comp))

Back to the CGW '97 home page

$Id: index.html,v 1.14 1997/01/10 01:07:12 kbrussel Exp $