Computer Graphics Workshop '97 Problem Set 3 | 1/10/97 |
Problems
The first step is to write the function compute-point-on-torus. The arguments to this function are:
This function should return an SbVec3f containing the x, y, and z coordinates of a point on the surface of the torus.
You can think of this computation as the sum of two 3D vectors. The first ("major") vector goes from the center of the torus to a center point of the "tube", and iterates (using the horizontal index as its counter) along the circle which defines the center of the tube. The second ("minor") vector goes from a center point of the tube to the surface of the torus, and iterates (using the vertical index as its counter) along the circle defined by a radial cross-section of the tube.
To help you along, here are procedure skeletons for computing these major and minor vectors. Note that the indices x-index and y-index provide identical results for 0, x-res, 2*x-res, ...
(define M_PI 3.14159265358979323846) ;; META-HINT: It's not hard. Use trigonometry. (define (compute-torus-major-vector major-radius x-res x-index) (let* ((theta (* (* 2.0 M_PI) (/ x-index x-res))) ;; YOUR CODE HERE to compute x, y, and z. ;; HINT: y is equal to 0.0. ) (new-SbVec3f x y z))) (define (compute-torus-minor-vector minor-radius x-res y-res x-index y-index) (let* ((theta (* (* 2.0 M_PI) (/ x-index x-res))) (phi (* (* 2.0 M_PI) (/ y-index y-res))) ;; YOUR CODE HERE to compute x, y, and z. ;; HINT: x and z depend on both phi and theta. ;; y depends only on phi. ) (new-SbVec3f x y z)))Now write compute-point-on-torus. This function simply calls compute-torus-major-vector and compute-torus-minor-vector, and returns the sum of these vectors. How do you compute the sum of two SbVec3fs? From the manual page:
SbVec3f operator +(const SbVec3f &v1, const SbVec3f &v2)
This is a method on an SbVec3f which takes the second SbVec3f as argument and returns the sum of the two vectors in a new vector. The syntax in Scheme is
(-> vec1 'operator+ vec2)(This syntax is the same for all operators except operator [] and operator (); those become, in Scheme, operator-brackets and operator-parens, respectively.)
(define (compute-torus-polygons major-radius minor-radius x-res y-res x-index y-index) (if (not (= y-index y-res)) (begin (let ((p1 (compute-point-on-torus major-radius minor-radius x-res y-res x-index y-index)) (p2 (compute-point-on-torus major-radius minor-radius x-res y-res x-index (1+ y-index))) (p3 (compute-point-on-torus major-radius minor-radius x-res y-res (1+ x-index) (1+ y-index))) (p4 (compute-point-on-torus major-radius minor-radius x-res y-res (1+ x-index) y-index))) (if (= x-index (- x-res 1)) (cons p1 (cons p2 (cons p3 (cons p4 (compute-torus-polygons major-radius minor-radius x-res y-res 0 (1+ y-index)))))) (cons p1 (cons p2 (cons p3 (cons p4 (compute-torus-polygons major-radius minor-radius x-res y-res (1+ x-index) y-index)))))))) '()))Now we can put the torus together. We will need at least three nodes to do so:
You will probably want to add more nodes to the above scene graph (such as an SoMaterial node) to be able to change more parameters of the torus (like its color).
We want the torus to be an "object" that we can interact with in the same way we interact with Open Inventor objects from within Scheme. You should use this wrapper for the "->" function to allow Scheme and C++ objects to share the same syntax, in a similar fashion to the object system used in the adventure game in 6.001.
;;; 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))Using this wrapper, and the structure for your Scheme objects described below, you can now use this "send" function to send messages to native Inventor objects and objects you have written in Scheme in the same way. For example:
> (define root (new-SoSeparator)) > (send root 'ref) > (define torus (new-Torus 10 3 20 10)) ;; new-Torus ;; defined below > (send root 'addChild (send torus 'getGeometry))Now define the "new-Torus" function, which is the constructor for a new Torus object:
(define (new-Torus major-radius minor-radius x-res y-res) (define root (new-SoSeparator)) (send root 'ref) (define coords (new-SoCoordinate3)) (define mat (new-SoMaterial)) (define face-set (new-SoFaceSet)) ;;; ... YOUR CODE HERE to build scene graph ... (let ((self (lambda (message) ;;; Method to return pointer to root of scene graph (cond ((eq? message 'getGeometry) (lambda (self) root)) ((eq? message 'generate) (lambda (self) (let* ((points (compute-torus-polygons major-radius minor-radius x-res y-res 0 0)) (num-points (length points))) ;; set-mfield-values! sets the values of the passed ;; mfield, starting at the given index, from the ;; passed list of values. ;; Note that the elements of this list must be valid ;; objects for use in the set1Value method. (set-mfield-values! (send coords 'point) 0 points) ;; Truncate list if necessary (i.e., when ;; reducing resolution) (send (send coords 'point) 'setNum num-points) ;; update the values in the face set: ;; (num-points / 4) polygons of four vertices each. (let loop ((i 0)) (send (send face-set 'numVertices) 'set1Value i 4) (if (< i (/ num-points 4)) (loop (1+ i)))) ;; Truncate numVertices as well, if necessary (send (send face-set 'numVertices) 'setNum (/ num-points 4))))) ;;; YOUR CODE HERE for other methods which include ;;; setColor, setMajorRadius, setMinorRadius, ;;; setHorizResolution, and setVertResolution. ;;; As an example: setMajorRadius ((eq? message 'setMajorRadius) (lambda (self new-major-radius) (set! major-radius new-major-radius) (send self 'generate))) ;;; After all other methods' definitions, ;;; if we haven't been able to find this method, ;;; signal an error (else (no-method message)))))) ;;; Initialization code goes here (send self 'generate) ;; generate when first constructed self))Each method takes the current object as its first argument (i.e. (lambda (self arg1 arg2 ...))). Note that as in the example, all of the "set" methods end with a call to
(send self 'generate)This allows the torus object to be operated upon in a similar fashion to other Inventor objects; when a "field" in the torus is changed (i.e. the major radius) the scene graph is automatically redrawn to reflect that change.
Look at the man page for SoShapeHints. It has three fields: vertexOrdering, shapeType, faceType, and creaseAngle. The creaseAngle field specifies (in radians) the maximum angle between adjacent polygons before they stop being smoothly shaded together. In Inventor 2.1 the default crease angle is 0; in previous releases it was 0.5. If your torus is not smoothly shaded, add a shape hints node to your scene graph and increase its crease angle to 0.6 or 0.7.
To make the torus render faster, you can enable backface culling; this prevents polygons not facing the camera from rendering. It is only appropriate to enable this feature for solid shapes; but since normally you can not see the "inside" of the torus, we can use it here.
Add a shape hints node to your graph and set the shapeType field to be SOLID:
(send (send shape-hints 'shapeType) 'setValue SoShapeHints::SOLID)Now, depending on whether you ordered the vertices in your polygons clockwise or counterclockwise with respect to the outside of the torus, set the vertexOrdering field to CLOCKWISE or COUNTERCLOCKWISE:
(send (send shape-hints 'vertexOrdering) 'setValue SoShapeHints::CLOCKWISE) ;; or: (send (send shape-hints 'vertexOrdering) 'setValue SoShapeHints::COUNTERCLOCKWISE)(If you have the wrong ordering, you will know: the inside of the torus will be visible on the screen, and will not look like a physically realizable object.) Add methods to your torus to be able to switch back and forth between the SOLID and UNKNOWN_SHAPE_TYPE shape types. Note the difference in rendering speed.
Look at the man page for SoQuadMesh. This node constructs a "sheet" of quadrilaterals out of a "grid" of coordinates, specified in rows. A torus can be constructed out of this sheet by attaching the top and bottom edges to make a cylinder and then "bending" the cylinder into a ring. In order to use an SoQuadMesh, we must change the list of coordinates we insert into the SoCoordinate3 node so they conform to this ordering. Here is a function, compute-torus-points, which returns an appropriate point list:
(define (compute-torus-points major-radius minor-radius x-res y-res) (define (vert-loop vert-step horiz-step) (if (<= vert-step y-res) (cons (compute-point-on-torus major-radius minor-radius x-res y-res horiz-step vert-step) (vert-loop (1+ vert-step) horiz-step)) '())) (define (horiz-loop step) (if (<= step x-res) (cons (vert-loop 0 step) (horiz-loop (1+ step))) '())) (let ((slices-list (horiz-loop 0))) (apply append slices-list)))Note that the horizontal and vertical loops go from 0 to x-res and y-res, respectively. This means that there are duplicate points along the "seams" of the torus. Without the duplicate points, the last row and column of polygons would not appear, and the torus would have two orthogonal "strips" missing (try it -- after you've got it working!). Modify the scene graph of your torus to add an SoQuadMesh node instead of the SoFaceSet node. Now modify the generate method of your torus to set up the verticesPerColumn and verticesPerRow fields of the QuadMesh node.
((eq? message 'generate) (lambda (self) (let* ((points (compute-torus-points major-radius minor-radius x-res y-res)) (num-points (length points))) ;; Set up Coordinate3 node as before (set-mfield-values! (send coords 'point) 0 points) (send (send coords 'point) 'setNum num-points) ;; YOUR CODE HERE to set up the verticesPerRow and ;; verticesPerColumn fields of the QuadMesh. ;; Note that verticesPerRow is equal to y-res+1, ;; and verticesPerColumn is equal to x-res+1 )))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.
$Id: index.html,v 1.13 1997/01/10 18:48:25 kbrussel Exp $