Computer Graphics Workshop '96 PS5 Solutions

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 min-normalized-radius 0.0001)
(define max-normalized-radius 1.0)
(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 (or (> r max-normalized-radius)
	      (< r min-normalized-radius))
	  ;; 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))
  (-> root 'addChild bind)
  ;; Each star gets its own color
  (-> (-> bind 'value) 'setValue SoMaterialBinding::PER_VERTEX)
  (define style (new-SoDrawStyle))
  (-> root 'addChild style)
  ;; Change the size of the stars' points
  (-> (-> style 'pointSize) 'setValue 2.0)
  (define coords (new-SoCoordinate3))
  (-> root 'addChild coords)
  ;; 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))
  (-> root 'addChild mat)
  ;; 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))
  (-> root 'addChild ps)
  ;; 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.
  (-> root 'addChild xform)
  (define ev (new-SoEventCallback))
  (-> root 'addChild ev)
  ;; 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.
  (-> ev 'addEventCallback
      (SoLocation2Event::getClassTypeId) 
      (get-scheme-event-callback-cb)
      (void-cast cb-info))
  ;; Add stars to the scene graph
  (-> root 'addChild (build-star-subgraph))
  ;; 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))
(-> root 'addChild cam-kit)

;; 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))
(-> root 'addChild ev)

;; 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
(-> root 'addChild (read-from-inventor-file "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"))
(-> ev 'addEventCallback
    (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"))
(-> ev 'addEventCallback 
    (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)

Back to the CGW '96 home page

$Id: index.html,v 1.3 1996/01/19 01:34:12 kbrussel Exp $