## 1/22/96

Problems
Problem 1 - Make a stellar viewer

```;;; Problem 5-1

(require 'random)

(define M_PI 3.14159265358979323846)

;; Parameters for shell of stars.

(define star-resolution 10000)
(define number-of-stars 1000)
(define r-min 30.0)
(define r-max 50.0)

;; Definition of stars' transform and transformation matrix.

(define xform (new-SoTransform))
(define xform-mat (new-SbMatrix))
(-> xform-mat 'makeIdentity)

;; Variables which get updated by the Location2Event callback
;; and used by the idle sensor callback.

(define last-event #f)
(define last-normalized-x-position 0.0)
(define last-normalized-y-position 0.0)

;; Maximum angular change about an axis per time step (in radians)

(define max-angular-velocity (/ M_PI 200.0))

;; Procedure which attempts to get some random state into the
;; random number generator.

(define (randomize-loop i)
(if (> (modulo i 173) 0)
(begin
(display "Randomizing...\n")
(random star-resolution)
(randomize-loop (- i 1)))))

;; Returns a vector of x, y, z coordinates of a point inside
;; the thick shell of stars.

(define (generate-star)
;; Pick random x, y, z between -1.0 and 1.0
(let ((x (- (* 2.0 (/ (random star-resolution)
star-resolution))
1.0))
(y (- (* 2.0 (/ (random star-resolution)
star-resolution))
1.0))
(z (- (* 2.0 (/ (random star-resolution)
star-resolution))
1.0)))
;; Calculate distance from origin
(let ((r (sqrt (+ (* x x) (* y y) (* z z)))))
;; Constrain to be within the normalized shell
;; If outside, try again
(generate-star)
;; Otherwise, scale up to desired radius
(let ((phi (asin (/ y r)))
(theta (atan z x))
(new-r (+ r-min (* (- r min-normalized-radius)
(- r-max r-min)))))
;; Return Cartesian coordinates of new point
(vector (* new-r (cos phi) (cos theta))
(* new-r (sin phi))
(* new-r (cos phi) (sin theta))))))))

;; Wrapper function for above -- returns
;; list of vectors of stars' coordinates

(define (generate-stars num-stars)
(if (> num-stars 0)
(cons (generate-star)
(generate-stars (- num-stars 1)))
'()))

;; Simple function which returns stars' colors.
;; Designed to return various shades of blue.

(define (generate-star-color)
(let ((blueness (exact->inexact (/ (random star-resolution)
star-resolution))))
(vector blueness blueness 1.0)))

;; Wrapper function for above which returns list of vectors of
;; stars' RGB colors.

(define (generate-star-colors num-colors)
(if (> num-colors 0)
(cons (generate-star-color)
(generate-star-colors (- num-colors 1)))
'()))

;; Procedure which builds the shell of stars.
;; Returns root of this scene graph.

(define (build-star-subgraph)
(define root (new-SoSeparator))
;; Note that we reference count the root for safety
(-> root 'ref)
(define bind (new-SoMaterialBinding))
;; Each star gets its own color
(-> (-> bind 'value) 'setValue SoMaterialBinding::PER_VERTEX)
(define style (new-SoDrawStyle))
;; Change the size of the stars' points
(-> (-> style 'pointSize) 'setValue 2.0)
(define coords (new-SoCoordinate3))
;; Set the coordinates inside the Coordinate3 node to the
;; result of the generate-stars procedure
(set-mfield-values! (-> coords 'point)
0
(generate-stars number-of-stars))
(define mat (new-SoMaterial))
;; Set the colors inside the Material node to the result of
;; generate-star-colors.
(set-mfield-values! (-> mat 'diffuseColor)
0
(generate-star-colors number-of-stars))
(define ps (new-SoPointSet))
;; Number-of-stars coordinates and colors to be rendered.
(-> (-> ps 'numPoints) 'setValue number-of-stars)
;; Return root to its original reference count (without deleting it)
(-> root 'unrefNoDelete)
;; Return root of the scene graph
root)

(define mouse-move-cb
;; Local variables.
;; Note that these are allocated only once for speed.
(let ((xvec (new-SbVec3f 1 0 0))
(yvec (new-SbVec3f 0 1 0))
(xrot (new-SbRotation))
(yrot (new-SbRotation))
(xmat (new-SbMatrix))
(ymat (new-SbMatrix)))
(lambda (user-data node)
;; Get event out of callback node
(let* ((event (SoLocation2Event-cast (-> node 'getEvent)))
;; Get normalized position of where event occurred
(loc (-> event 'getNormalizedPosition
(-> viewer 'getViewportRegion)))
;; Change so (0,0) is in center of window
(delta (-> loc 'operator- (new-SbVec2f 0.5 0.5))))
;; Update last position where events occurred
(set! last-normalized-x-position
(-> delta 'operator-brackets 0))
(set! last-normalized-y-position
(-> delta 'operator-brackets 1))
;; Calculate incremental updates to x and y angles
(let ((x-angle (* max-angular-velocity
last-normalized-y-position))
(y-angle (* max-angular-velocity
last-normalized-x-position)))
;; Update SbRotations with correct angle values
(-> xrot 'setValue xvec x-angle)
(-> yrot 'setValue yvec y-angle)
;; Update SbMatrix objects with rotations' new values
(-> xmat 'operator= xrot)
(-> ymat 'operator= yrot)
;; Update the transform matrix incrementally in two steps
(-> xform-mat 'operator*= xmat)
(-> xform-mat 'operator*= ymat)
;; Set the transform's matrix to be this new matrix
(-> xform 'setMatrix xform-mat))))))

(define idle-cb
(let ((xvec (new-SbVec3f 1 0 0))
(yvec (new-SbVec3f 0 1 0))
(xrot (new-SbRotation))
(yrot (new-SbRotation))
(xmat (new-SbMatrix))
(ymat (new-SbMatrix)))
(lambda (user-data sensor)
;; Tell sensor to reschedule
(-> sensor 'schedule)
;; Note that we make the x and y angles update faster inside
;; the idle callback. Otherwise, we we notice that the stars
;; rotate faster when the mouse is in motion.
(let ((x-angle (* 3.0 max-angular-velocity
last-normalized-y-position))
(y-angle (* 3.0 max-angular-velocity
last-normalized-x-position)))
;; Exactly the same code as in the previous callback
(-> xrot 'setValue xvec x-angle)
(-> yrot 'setValue yvec y-angle)
(-> xmat 'operator= xrot)
(-> ymat 'operator= yrot)
(-> xform-mat 'operator*= xmat)
(-> xform-mat 'operator*= ymat)
(-> xform 'setMatrix xform-mat)))))

;; Builds the entire scene graph, including stars' transform,
;; event callback node, and sphere of stars.

(define (build-scene-graph)
;; Randomizing state of the random number generator
(randomize-loop (get-universal-time))
(define root (new-SoSeparator))
(-> root 'ref)
;; xform is defined above; the global transform node for the stars.
;; Modified by both callbacks above.
(define ev (new-SoEventCallback))
;; Creating callback information node (same as for sensors).
(define cb-info (new-SchemeSoCBInfo))
(-> cb-info 'ref)
(-> (-> cb-info 'callbackName) 'setValue "mouse-move-cb")
;; Adding Location2Event callback to the event callback node.
(SoLocation2Event::getClassTypeId)
(get-scheme-event-callback-cb)
(void-cast cb-info))
;; Add stars to the scene graph
;; Note that we do not unref this root node
root)

(define viewer (new-SoXtExaminerViewer))
(-> viewer 'show)
(-> viewer 'setSceneGraph (build-scene-graph))
;; Set the viewer's camera to be
;; at the center of the sphere of stars
(-> (-> (-> viewer 'getCamera) 'position) 'setValue 0 0 0)
;; Select the arrow icon in the viewer
(-> viewer 'setViewing 0)

;; Callback information node for the idle sensor's callback
(define cb-info (new-SchemeSoCBInfo))
(-> cb-info 'ref)
(-> (-> cb-info 'callbackName) 'setValue "idle-cb")

;; Creation and scheduling of the idle sensor
(define idle-sensor (new-SoIdleSensor (get-scheme-sensor-cb)
(void-cast cb-info)))
(-> idle-sensor 'schedule)
```
Problem 2 - Make a fly viewer

```;;; Problem 5-2.

(define M_PI 3.14159265358979323846)

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

;; Creation of root of scene graph

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

;; Creation of camera kit.
(define cam-kit (new-SoCameraKit))

;; Initialization code for camera kit.
(define cam (SO_GET_PART cam-kit "camera" SoCamera))
(-> (-> cam 'nearDistance) 'setValue 1.0)
(-> (-> cam 'farDistance) 'setValue 10000.0)
(define transform (SO_GET_PART cam-kit "transform" SoTransform))

;; Event callback node for mouse button and movement events.
(define ev (new-SoEventCallback))

;; Addition of the scene; something to look at.
;; "room.iv" must be in the current directory; you can
;; copy it from /mit/thingworld/Ivy/room.iv

;; Set scene graph and select arrow icon in viewer.
(-> viewer 'setSceneGraph root)
(-> viewer 'setViewing 0)

;; Global variables.

(define *max-angular-velocity* (/ M_PI 200.0))  ;; in radians/sec
;; Matrix for translation of camera; initialize
(define *translate-matrix* (new-SbMatrix))
(-> *translate-matrix* 'makeIdentity)
;; Matrix for rotation of camera about its origin
(define *rotate-matrix* (new-SbMatrix))
;; Initialize rotation matrix
(-> *rotate-matrix* 'makeIdentity)
;; Global angles for rotations about x and y axes
(define *x-angle* 0.0)
(define *y-angle* 0.0)

;; Increment of speed depending on mouse button presses.
;; Note that this is negative, because the camera's negative z
;; axis goes into the screen.
(define *forward-increment* -1)
(define *forward-speed* (* 3 *forward-increment*))

;; Has there been a mouse movement event yet?
(define *last-event* #f)

;; Last positions of mouse within window
(define *last-normalized-x* 0.0)
(define *last-normalized-y* 0.0)

;; Callback for Location2Events. Very similar to problem 1.

(define mouse-move-cb
;; Local variables; only allocated once for speed.
(let ((xmat (new-SbMatrix))
(ymat (new-SbMatrix))
(zmat (new-SbMatrix))
(zvec (new-SbVec3f))
(xrot (new-SbRotation))
(xvec (new-SbVec3f 1 0 0))
(yrot (new-SbRotation))
(yvec (new-SbVec3f 0 1 0)))
(lambda (user-data node)
;; Get event out of event callback node
(let* ((event (SoLocation2Event-cast (-> node 'getEvent)))
;; Get normalized position within window of this event
(loc (-> event 'getNormalizedPosition
(-> viewer 'getViewportRegion)))
;; Change origin to be at center of window
(delta (-> loc 'operator- (new-SbVec2f 0.5 0.5))))
;; Update global angles depending on
;; mouse's position in window
(set! *x-angle*
(+ *x-angle*
(* *max-angular-velocity*
(-> delta 'operator-brackets 1))))
(set! *y-angle*
(+ *y-angle*
(* *max-angular-velocity*
(-> delta 'operator-brackets 0))))

;; Change SbRotations to use these new angles
;; Note that the angles are negated -- found this
;; through trial and error.
(-> xrot 'setValue xvec (- *x-angle*))
(-> yrot 'setValue yvec (- *y-angle*))

;; Set up the matrices using the computed SbRotations
(-> xmat 'setRotate xrot)
(-> ymat 'setRotate yrot)

;; Set the global rotation matrix equal to
;; the product of the x and y matrices in two steps.
;; Note that this is not incremental
;; updating as in the first problem.
(-> *rotate-matrix* 'operator= xmat)
(-> *rotate-matrix* 'operator*= ymat)

;; Creation of the world coordinate system vector
;; along which the camera will travel.

;; Create local coordinate system vector
(-> zvec 'setValue 0 0 *forward-speed*)
;; "Magic" line to transform vector
;; into world coordinate system.
;; Found basically by trial and error.
(-> (-> *rotate-matrix* 'inverse) 'multMatrixVec zvec zvec)
;; Set translation of final matrix to be this vector
(-> zmat 'setTranslate zvec)
;; Update global translation matrix by this
;; vector through multiplication
(-> *translate-matrix* 'operator=
(-> zmat 'operator* *translate-matrix*))

;; Set global transformation to be this matrix
(-> transform 'setMatrix
(-> *rotate-matrix* 'operator* *translate-matrix*))

;; Update variables to allow idle callback to run
(set! *last-event* #t)
(set! *last-normalized-x*
(-> delta 'operator-brackets 0))
(set! *last-normalized-y*
(-> delta 'operator-brackets 1))))))

;; MouseButtonEvent callback. Increases forward velocity of camera on
;; left mouse button press, decreases on middle button press.

(define (mouse-press-cb user-data node)
(let ((event (-> node 'getEvent)))
(cond ((= 1 (SO_MOUSE_PRESS_EVENT event BUTTON1))
(begin
(set! *forward-speed*
(+ *forward-speed* *forward-increment*))
(-> node 'setHandled)))
((= 1 (SO_MOUSE_PRESS_EVENT event BUTTON2))
(begin
(set! *forward-speed*
(- *forward-speed* *forward-increment*))
(-> node 'setHandled))))))

;; Callback for idle sensor.

(define idle-cb
(let ((xmat (new-SbMatrix))
(ymat (new-SbMatrix))
(zmat (new-SbMatrix))
(zvec (new-SbVec3f))
(xrot (new-SbRotation))
(xvec (new-SbVec3f 1 0 0))
(yrot (new-SbRotation))
(yvec (new-SbVec3f 0 1 0)))
(lambda (user-data sensor)
;; Reschedule sensor
(-> sensor 'schedule)
(if *last-event*
;; Almost exactly the same code as in the mouse movement
;; callback for the case where the mouse has been present
;; in the window.
(begin
(set! *x-angle*
(+ *x-angle*
(* 2
*max-angular-velocity*
*last-normalized-y*)))
(set! *y-angle*
(+ *y-angle*
(* 2
*max-angular-velocity*
*last-normalized-x*)))

(-> xrot 'setValue xvec (- *x-angle*))
(-> yrot 'setValue yvec (- *y-angle*))

(-> xmat 'setRotate xrot)
(-> ymat 'setRotate yrot)
(-> *rotate-matrix* 'operator= xmat)
(-> *rotate-matrix* 'operator*= ymat)

;; Change forward velocity to be faster in the idle
;; callback. This attempts to correct for the observed
;; effect that the camera moves faster when the
;; mouse is moving.
(-> zvec 'setValue 0 0 (* 2 *forward-speed*))
(-> (-> *rotate-matrix* 'inverse)
'multMatrixVec zvec zvec)
(-> zmat 'setTranslate zvec)
(-> *translate-matrix* 'operator=
(-> zmat 'operator* *translate-matrix*))

(-> transform 'setMatrix
(-> *rotate-matrix* 'operator*
*translate-matrix*)))))))

;; Set up callback for mouse movements (i.e. Location2Events).
;; Add callback to the EventCallback node.
(define cb-info (new-SchemeSoCBInfo))
(-> cb-info 'ref)
(-> (-> cb-info 'callbackName) 'setValue
(new-SbString "mouse-move-cb"))
(SoLocation2Event::getClassTypeId)
(get-scheme-event-callback-cb)
(void-cast cb-info))

;; Set up callback for mouse button events (i.e. MouseButtonEvents).
;; Add callback to the EventCallback node.
(define cb-info (new-SchemeSoCBInfo))
(-> cb-info 'ref)
(-> (-> cb-info 'callbackName) 'setValue
(new-SbString "mouse-press-cb"))
(SoMouseButtonEvent::getClassTypeId)
(get-scheme-event-callback-cb)
(void-cast cb-info))

;; Set up idle sensor callback.
(define cb-info (new-SchemeSoCBInfo))
(-> cb-info 'ref)
(-> (-> cb-info 'callbackName) 'setValue (new-SbString "idle-cb"))
(define idle-sensor (new-SoIdleSensor (get-scheme-sensor-cb)
(void-cast cb-info)))
(-> idle-sensor 'schedule)
```