Computer Graphics Workshop '96 Problem Set 4
Restructure your torus to use an SoQuadMesh instead of a face set. This involves the writing of the function compute-torus-points, which instead of returning a list of the coordinates of all the polygons should return a list of the vertices on the torus. You may loop over either the horizontal or vertical dimension first.
Once that is done, you can replace the face set with a quad mesh. This should involve only two steps; telling the program how many rows and columns are in the vertex list. Be extra careful when setting these values, because an "off by one" error will probably cause Inventor to crash. Hint: in order for your torus to show up properly you must repeat the first set of vertices as the last set of vertices in each loop. This makes the number of rows and columns equal to the horizontal or vertical resolution plus one.
After you have finished the above steps and have gotten the torus to render using the quad mesh, you may have to change the vertexOrdering field of your ShapeHints node if your torus shows up very dark on the screen.
We give you the basic scene graph you should use here:
(define viewer (new-SoXtExaminerViewer)) (-> viewer 'show) (define root (new-SoSeparator)) (-> root 'ref) (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) (-> viewer 'setSceneGraph root)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.
As in the case of the torus, we will be using the SICP-like object system defined in problem set 3. You may want to save the following definitions in a file and load them in at the top of your problem st 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) 'no-method) (define (no-method? method) (eq? method 'no-method))The scene graph for the ValueSlider will look like this:
root (SoSeparator) | ----------------------------- | | | 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 (using 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. Later on you will be printing the value of the slider using this text; you can get a string out of a floating-point value by calling the standard Scheme number->string function.
Once you have these parameters down, you are ready to write the constructor for a new ValueSlider object. This object will take at least the following parameters:
The beginning of the new-ValueSlider function will look like the following:
(define (new-ValueSlider min-value max-value current-value title) (define root (new-SoSeparator)) (send root 'ref) ;; Build scene graph and set up initial position of draggerOnce you have built the scene graph for the ValueSlider inside the constructor, you are ready to implement the methods for the object.
(lambda (message) (cond ((eq? message 'getGeometry) (lambda (self) root))Remember that all methods take a pointer to the current object 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 value changed 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:
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. Recall the method we introduced for setting up callbacks from within Scheme: we use the C++ "wrapper function" as the function pointer (obtained with the call here of (get-scheme-dragger-cb)), and a SchemeSoCBInfo node as the data for this callback. This node contains the information about which Scheme callback should be called.
Write the method getCallbackInfo, which takes the name of the callback as argument. It should create a new SchemeSoCBInfo node, increment its reference count, set the value of its callbackName field to the passed callback name, and return this node.
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)Because of some poor design decisions (for which I apologize), all Scheme callbacks to be used by Inventor must be referenced by name; that is, there must be a name in the global environment pointing to the callback function. Therefore setting up the actual value changed callback for the dragger is a two step process.
NOTE: the following paragraph has been obsoleted. Please see the one following it for the correct problem.
(send dragger 'addValueChangedCallback (get-scheme-dragger-cb) (void-cast callback-info))
(send dragger 'addMotionCallback (get-scheme-dragger-cb) (void-cast callback-info))Now we are ready to write the updateValue method, which is called by the value changed callback. This method takes no arguments. It should get the value 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.
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. Note that the text does not change yet; we have not set up the value changed callback. Since doing so is a kludge, this part of the problem is a freebie:
(define viewer (new-SoXtExaminerViewer)) (send viewer 'show) (define my-slider (new-ValueSlider 0 10 5 "My slider")) (send viewer 'setSceneGraph (send my-slider 'getGeometry)) ;; get the callback defined in the global environment (define my-slider-callback (send my-slider 'getValueChangedCallback)) ;; pass in this definition to the addValueChangedCallback method (send my-slider 'addValueChangedCallback "my-slider-callback")Now the text should update when 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.
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")) (define minor-radius-slider-callback (send minor-radius-slider 'getValueChangedCallback)) (send minor-radius-slider 'addValueChangedCallback "minor-radius-slider-callback") ;; callback which changes torus' minor radius size (define (torus-minor-radius-callback new-value) (send my-torus 'setMinorRadius new-value)) ;; adding this callback to the ValueSlider (send minor-radius-slider 'addOutputCallback torus-minor-radius-callback)Whenever the value of the slider changes, the torus' minor radius will change to this new value.
NOTE: the following paragraph has been obsoleted. Please see the one following it for the corrected problem. (The method described in the following paragraph only works for Inventor 2.1, which is not available on Athena.)
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 additions 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).
$Id: index.html,v 1.4 1996/01/23 02:45:48 kbrussel Exp $