Computer Graphics Workshop '97 Problem Set 4


Problem 1 - Cannon fodder

In this problem we will see how to use a field sensor to keep track of the movement of some objects in the scene. We will recreate a scene reminiscent of the old game "Cannon Fodder" - there will be a moving target (user-controlled through a HandleBoxManip), and every time the target comes into the line of fire, it turns red.

We give you the basic scene graph you should use here:

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

(define viewer (examiner root))
(-> viewer 'setViewing 0)

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

; (define manip (new-SoTrackballManip))
; (-> root 'addChild manip)

(define cannon-base (new-SoCube))
(-> (-> cannon-base 'width) 'setValue 4)
(-> cannon-sep 'addChild cannon-base)

(define cannon-xform (new-SoTransform))
(-> cannon-sep 'addChild cannon-xform)
(-> (-> cannon-xform 'translation) 'setValue 0 4.0 0)

(define cannon-tube (new-SoCylinder))
(-> cannon-sep 'addChild cannon-tube)
(-> (-> cannon-tube 'height) 'setValue 6.0)

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

(define target-manip (new-SoHandleBoxManip))
(-> target-sep 'addChild target-manip)
(-> (-> target-manip 'translation) 'setValue 5 15 2)

(define target-mat (new-SoMaterial))
(-> target-sep 'addChild target-mat)
; (-> (-> target-mat 'diffuseColor) 'setValue 0.8 0.8 0.8) 
;; (this is the default value)

(define target (new-SoSphere))
(-> (-> target 'radius) 'setValue 0.5)
(-> target-sep 'addChild target)
Hook up a field sensor to the translation field of target-manip. The callback for this sensor should get the current translation out of the target-manip node and figure out whether the target is in the line of fire. The line of fire goes up along the y axis (that is, the y axis is in the center of the tube of the cannon). When the target is in the line of fire, it should turn red.

You may handle border cases in any way you like, but the target should definitely not be red when it is behind the cannon.

This is a short problem and should not require more than about 20 lines of code.

Problem 2 - User interface for the torus

In this problem we will create a new type of "slider widget" object in Scheme. This object will consist of a text object for its title and (floating point) value, and an SoTranslate1Dragger for modifying the value. We will then use this new object to create an interactive user interface for the torus. This is a long problem, and we recommend you get started on it early.

As in the case of the torus, we will be using the SICP-like object system defined in problem set 3. If you aren't working on Athena, you should save the following definitions in a file and load them in at the top of your problem set file:

;;; SICP-ish simple object system for dealing similarly
;;; with Scheme and C++ objects using "send"

(define (send object message . args)
  (if (C++-object? object)
      (eval `(-> ,object ',message ,@args))
      (let ((method (get-method object message)))
	(if (not (no-method? method))
	    (apply method (cons object args))
	    (error "No method named" message)))))

(define (get-method object message)
  (object message))

(define (no-method method)

(define (no-method? method)
  (eq? method 'no-method))
The internal scene graph for the ValueSlider will look like this:

      |             |             |
    font          text         dragger
  (SoFont)      (SoText3)(SoTranslate1Dragger)
The SoFont and SoText3 nodes are used in tandem to provide 3D textual annotation in the scene. The text node will be used to print the title text and the current value for the ValueSlider. If you want the text to show up on the left side of the dragger, as opposed to above it, you will need to insert an SoTransform node between the text and dragger nodes to offset the position of the dragger from the text.

Create the above scene graph. Note that the SoText3 node contains an SoMFString field called string: this contains the text to be displayed. You can change the visible text by calling the set1Value method of SoMFString. Note that this method takes an SbString as its second argument, so you must do the following, for example (using the above "send" syntax):

(send (send text 'string) 'set1Value 0 (new-SbString 
					"First line of text"))
(send (send text 'string) 'set1Value 1 (new-SbString
					"Second line of text"))
Adjust the font size and the vertical position of the dragger (by setting its translation field) until the dragger and text are in proportion. (Using the default values, the dragger will be much smaller than the text.) You can change the justification of the text by changing the justification field of the text node.

Once you have these parameters down, you are ready to write the constructor for a new ValueSlider object. This object will have at least the following parameters:

The skeleton of the new-ValueSlider function should look like the following:

(define (new-ValueSlider min-value max-value current-value title)
  (define root (new-SoSeparator))
  (send root 'ref)
  ;; YOUR CODE HERE to build internal scene graph for ValueSlider.

  ;; YOUR CODE HERE to define instance variables.
  ;; Note that you can use set! on the current-value variable above
  ;; to keep track of the ValueSlider's current position.

  ;; Message passing system
  (let ((self
	 (lambda (message)
	   ;; Methods:
	    ((eq? message 'getGeometry)
	     (lambda (self)
	    ;; YOUR CODE HERE for other methods (described below).
    ;; YOUR CODE HERE to initialize dragger
    ;; (using "send self message" to acquire dragger callback).
    ;; Return ValueSlider object
Once you have built the scene graph for the ValueSlider inside the constructor, you are ready to implement the methods for the object. Remember that all methods take a self parameter as their first argument.

The getGeometry method returns a pointer to the root of the scene graph of the ValueSlider object. This can then be added to other scene graphs as a "fully encapsulated" new type of Inventor object.

Write the getValue method, which returns the current-value instance variable.

In order for this new object to be useful, we need to have three features:

To implement all three of these items, we must have some notification when the dragger has been moved. We will therefore add a motion callback to the dragger, whose purpose will be to call the updateValue method of our ValueSlider. Look at the manual page for SoDragger. Note the definition of an SoDraggerCB:

typedef void SoDraggerCB(void *userData, SoDragger *dragger)

Therefore a Scheme dragger callback function must take two arguments, the user data field (which we will not use) and a pointer to the relevant dragger.

Write the getValueChangedCallback method, which takes no arguments (that is, it takes only the "self" pointer as argument). This method will return the function to be called whenever the dragger is moved. Since this function is contained within the ValueSlider object, it can call all the methods of this specific ValueSlider instance. To sum up, this method should return a function of two arguments (user-data and dragger); the body of this function should be the call

(send self 'updateValue)
Now we are ready to write the updateValue method, which is called by the motion callback. This method takes only the self argument. It should get the value of the translation field (an SbVec3f), and get the x coordinate out of this vector (you can use the operator-brackets method for this). It should change the current-value variable using set!, and then update the value displayed in the text object. (Use the Scheme function number->string to get a string out of the current value.)

There is only one more step before we have something on the screen: the initialization code for the dragger. This (one line of) code sets the motion callback for the dragger to be the result of evaluating the getValueChangedCallback method. For a hint, see today's lecture notes.

At this point you should be able to create a new ValueSlider widget, call the getGeometry method to get the root of its scene graph, and view that in a viewer. The text should change as the slider moves.

We still have two more goals to fulfill: constraining the movement of the slider and getting it to update some other object's field. We will do the second of these first. Create a variable (in the same place as the "Build scene graph" code) called callback-list, which should be initialized to '(), the empty list. Write a method, addOutputCallback, which takes a function, callback-function, as argument, and adds it to the callback list.

Now add a section to the updateValue method which calls each of the functions in the callback list with current-value as their argument. (Hint: use for-each.)

At this point you should be able to hook up a slider to, say, the minor radius of the torus, using something like the code here:

;; ...creation of the viewer, torus, etc...

(define minor-radius-slider
  (new-ValueSlider 0 10 3 "Minor radius"))

;; add a callback which changes my-torus' minor radius size
;; to the ValueSlider
(send minor-radius-slider 'addOutputCallback
      (lambda (new-value)
	(send my-torus 'setMinorRadius new-value)))
Whenever the value of the slider changes, the torus' minor radius will change to this new value.

The final improvement we need to add is the constraint that the slider must remain between the minimum and maximum values. We can do this within the updateValue method as well. All we need to do is boundary check the horizontal component of the translation and set it to the maximum or minimum if either is exceeded. Add code to the updateValue method to perform this boundary check, and to set the ValueSlider's internal notion of the current value as well as the translation of the dragger.

Now you should have a complete slider widget which can be used to affect "fields" in other Scheme objects. Hook up one widget to the major radius of a torus and another to the minor radius. Other features you may want to add are scaling factors for the widget (so the value will be some multiple of its translation) and integer-valued sliders (to be able to affect the resolution of the torus without doing the conversion in the callback).

Back to the CGW '97 home page

$Id: index.html,v 1.8 1997/01/15 18:23:54 kbrussel Exp $