Computer Graphics Workshop '96 Lecture Notes
Assume you have a scene graph rooted at the node "root". Then to write this scene graph out to the file "scene.iv", you would do the following:
(write-to-inventor-file root "scene.iv")To read in a file named "scene-in.iv" and define the root node to be "root-in", you would do the following:
(define root-in (read-from-inventor-file "scene-in.iv"))Let's examine what these procedures do to get a feeling for how functions like this would be performed in Open Inventor.
(define read-from-inventor-file (let ((my-scene-input (new-SoInput))) (lambda (filename) (if (= 0 (-> my-scene-input 'openFile filename)) (begin (display "Cannot open file ") (display filename) (display "\n") (SoSeparator-cast (void-null))) (begin (let ((my-graph (SoDB::readAll my-scene-input))) (if (equal? my-graph (SoSeparator-cast (void-null))) (begin (display "Problem reading file\n") (SoSeparator-cast (void-null))) (begin (-> my-scene-input 'closeFile) my-graph))))))))When the function read-from-inventor-file is defined, it creates a local variable my-scene-input which is an object of type SoInput. This object is used to open the file for reading, and is passed as argument in the call to SoDB::readAll. This is a function of the scene database which does the following: if one scene graph is defined in the .iv file and its root node is a separator, it reads in this scene graph and returns a pointer to the root node; otherwise, it reads in all the scene graphs defined inside the file and places them under a newly created root separator node, which is then returned. Note the several levels of error checking; if the function fails, it will return a NULL pointer of type SoSeparator. You can check programmatically whether the call to read-from-inventor-file has failed by writing
(define root (read-from-inventor-file "scene.iv")) (if (equal? root (SoSeparator-cast (void-null))) (display "Read failed!") (display "Read succeeded."))Note that this function reuses the same SoInput object over and over again; this saves time and space that would be consumed by memory allocation and garbage collection.
As you might expect, there is an analogue of SoInput used for file output, which is called SoOutput. The procedure for writing a scene graph is as follows: construct the output object and open the destination file, create a write action object, tell this action to use the output object as its destination, and apply the action to the root node of the scene graph. The implementation is then straightforward:
(define (write-to-inventor-file root filename) (let* ((out (new-sooutput)) (wa (new-sowriteaction out))) (if (= 1 (-> out 'openfile filename)) (begin (-> wa 'apply root) (-> out 'closefile) 'done) (begin (display "Error opening file for output") 'error))))Note that this is the general procedure for performing actions on a scene graph: construct the action, set up the destination for its side-effects, if any, and apply the action to a certain node. (Note also that we were less careful about avoiding creating garbage when writing this procedure.)
Let's look at an example of this. Consider the following scene graph:
Separator | ------------- | | Group Cone | Material (blue)Keep in mind that the default material for all shapes is white. During rendering of this scene graph, the nodes will be visited in the following order: Separator, Group, Material, and Cone. The Material node will change the current color in the rendering state to blue, and this color will then be used when rendering the cone. However, if we replaced the Group node with another Separator, the blue material would be "popped" off the stack when we finished traversing all of the children of this second separator. For this reason, the cone would appear white in this case. The last type of separator node is called a TransformSeparator; this implements a push/pop as described above, but only for the current transformation matrix. That is, if there is a transform node under a transform separator which moves geometry under that separator around, this transformation will not affect objects above and to the right of the transform separator. However, changes to other elements such as the current color will be propagated past the transform separator.
There are several other subclassses of group nodes which provide other specialized functions, but most, if not all, of them are derived from these base classes described above. Two of these nodes are SoSwitch and SoBlinker. A switch node traverses all, none, or exactly one of its children. A blinker node traverses one of its children during each render cycle, but changes which child it traverses a specified number of times per second.
All of the types of group node simplement the same base-level functionality for adding children to and removing children from a group:
The addChild method takes a node as an argument, and adds this node as the last child of this group. The insertChild method takes a node and an index as arguments, and adds this node so that it becomes the one with the given index. (The first child of a group has index 0.) The removeChild method can take either a node or index as an argument, and attempts to remove the indicated node grom the group. Keep in mind the issues of reference counting described in the last lecture; if you are not careful, removing a child of a group will cause this child to be deleted.
A vertex based shape in general is created by first specifying the coordinates of the vertices in the shape, and then specifying how these coordinates will be used. As an example, let's create a face set, which is a collection of planar polygons.
The structure of our scene graph will look like this:
root (SoSeparator) | ------------------------------------------------- | | | | coords bind mat face-set (SoCoordinate3) (SoMaterialBinding) (SoMaterial) (SoFaceSet)The "coords" node replaces the current coordinates in the rendering state with those in the node. The "bind" node specifies how the colors to come will affect the resulting polygons; that is, whether the colors will affect each vertex, each polygon, or the entire set of polygons. The "mat" node specifies the colors for the face set. The "face-set" node groups the current coordinates and material properties into appropriately colored polygons. (Note that it is valid to change the ordering of the first three nodes, but that the FaceSet node must come last, because traversal of this node causes a shape to be drawn immediately.)
Now let's build a sample scene graph using this structure.
(define root (new-SoSeparator)) (-> root 'ref) (define coords (new-SoCoordinate3)) (-> root 'addChild coords) (define bind (new-SoMaterialBinding)) (-> root 'addChild bind) (define mat (new-SoMaterial)) (-> root 'addChild mat) (define face-set (new-SoFaceSet)) (-> root 'addChild face-set) ;; set up the coordinates for our polygons (set-mfield-values! (-> coords 'point) 0 '(#(1 0 0) #(2 0 0) #(1.5 1 0) #(0.5 1 0) #(0 0 0) #(1 0 0))) ;; make the material binding per face for now (-> (-> bind 'value) 'setValue SoMaterialBinding::PER_FACE) ;; set up colors -- enough for per vertex binding (set-mfield-values! (-> mat 'diffuseColor) 0 '(#(0 0 1) #(1 0 1) #(1 0 0) #(1 1 0) #(0 1 0) #(0 1 1))) ;; set up the polygons -- two triangles (set-mfield-values! (-> face-set 'numVertices) 0 '(3 3))When we view the resulting scene graph, we see two adjacent triangles of different colors. The face set tells the renderer how many of the specified coordinates to use for each polygon; the first polygon uses the first three coordinates, and the second uses the second three.
Now let's see what happens if we change this specification:
(-> (-> face-set 'numVertices) 'setValue 5)We see a polygon which uses the first five specified coordinates as the vertices of a polygon. Note that since the first and last points overlap, we do not use both of them.
As a final example, let's try changing the material binding of the scene:
(-> (-> bind 'value) 'setValue SoMaterialBinding::PER_VERTEX)Now each vertex has a color specified, and the renderer smoothly interpolates the color of the polygon to match the vertex colors.
The third problem set consists of the creation of a torus as a vertex based shape.
$Id: index.html,v 1.6 1996/01/12 18:11:31 kbrussel Exp $