Let's do a little animation by simply slightly modifying the scene we ended up with in Lesson 1 and drawing it within a loop. We do not need to worry about the 4x4 matrix, since we can use high level functions that perform basic transforms, such as rotation, scaling, movement etc. To rotate the sphere around the y-axis, we send a message :y-rotate-post to the matrix:
(defvar step) (setq step 5) (defvar cycles) (setq cycles 2)
(do ((i 0 (setq i (+ i step)))) ((> i (* 360 cycles))) (send matrix :y-rotate-post :degrees step) ;; draw the scene (send camera :clear-zbuffer) (send camera :clear-viewport) (send camera :draw :THINGS thing-list) (send camera :swap-buffers) )
Note that we don't use :y-rotate message for the reason that it starts with the fresh identity transform matrix each time, while the :y-rotate-post message takes the existing transform matrix for this thing and applies rotation.
If you are working on a biological structure department SGI, you should
be able to run this animation demo by clicking this link: demo1.
The animation above does not represent the usual way of repetitively updating the screen. It is more common to start an infinite loop, in which the screen can be modified and updated depending on the input from the user, and the loop can be terminated when necessary.
Let's look at an example of the more typical approach. Download the animated sphere demonstration by clicking on the link: demo2 and saving it local to your hard drive. You can open it up and look at the code (it's just an ASCII text file) and run the demo by typing skandha4 sphere-demo2.xS4 at your prompt once you have changed to the subdirectory where you saved the file.
Let's review the code:
We can use XG.3D-FRAME-LOOP function to set up the frame loop. First, we would need to specify the way out of the loop as follows:
(setq XG.3D-KEYBOARD-CHAR-HOOK (lambda nil (if (char= xg.3d-keyboard-char #\x) (setq xg.3d-frame-loop-done t))))
(format t "Hit 'x' in graphics window to exit~%" )
The first expression provided a XG.3D-KEYBOARD-CHAR-HOOK function, which reads the characters pressed on the keyboard. In case it was x, our hook function will set the variable xg.3d-frame-loop-done to t. In turn, XG.3D-FRAME-LOOP function checks this flag to see if the loop should be terminated. As a result, clicking the x key on the keyboard will terminate the frame loop. The second expression simply prints out:
Hit 'x' in graphics window to exit
Earlier, we used the syntax (send camera :draw :THINGS thing-list) to draw the scene, therefore explicitly providing the thing-list. Inside the XG.3D-FRAME-LOOP function, however, the :draw command will be called without any arguments. There is another way to draw the things. We can make the thing-list a property of the camera as follows:
(send camera :set :things thing-list)
Now things on the thing-list will be drawn automatically by the :draw command with no arguments. We can now load the XG.3D-FRAME-LOOP function:
(require "xg.3d-frame-loop")
And start the loop:
(XG.3D-FRAME-LOOP :FULL-WINDOW-CAMERA camera :CAMERA-LIST (list camera))
This function allows using more than one camera for drawing (for example,
different cameras can be used to redraw different parts of the window).
It also requires one camera object that can be used to clear the whole
window. In this case, we use the same camera to do
both.
While the scene is being redrawn within the loop, we can modify it using various hooks. For example let's create a hook that modifies the sphere when the user clicks on it:
(defun mouse-toggle-thing nil (let* ((obj (:get xg.3d-current-camera :selected-thing)) (material (xg.3d-get obj :material))) (:set material :diffuse-color (reverse (:get material :diffuse-color))) ))
(xg.3d-set sphere :downclick-hook #'mouse-toggle-thing)
This function changes the diffuse color of the sphere by reversing the list of color values (red green blue) whenever the user clicks on it.
Note the unfamiliar syntax of the expressions starting with (:get some-object... or (:set some-object... . This is a convenience of Skandha4 which allows shorthand syntax for expressions like (send some-object :get... or (send some-object :set... of Xlisp.
Similarly, we can define functions that would modify the sphere whenever the cursor is over it:
(defun select-thing nil (let* ((obj (:get xg.3d-current-camera :selected-thing)) (material (xg.3d-get obj :material))) (send material :set :EMISSION-COLOR (:get material :ambient-color)) ))
(defun deselect-thing nil (let* ((obj (:get xg.3d-current-camera :selected-thing)) (material (xg.3d-get obj :material))) (:set material :EMISSION-COLOR '(0 0 0)) ))
(xg.3d-set sphere :select-hook #'select-thing) (xg.3d-set sphere :deselect-hook #'deselect-thing)
Similar to the demo above, we could achieve animation effect by making small modification to the scene each time it is redrawn. In the example above, we used variable step to calculate these modifications. With the frame loop, we can provide :PER-FRAME-HOOK function, which will be executed once per frame, and use the :frame-number as a variable:
(SEND camera :SET :PER-FRAME-HOOK # (lambda nil (let ((frame-num (:get camera :frame-number))) (:scale matrix (+ 1 (* 0.3 (sin (* 0.10 frame-num)))) (+ 1 (* 0.3 (sin (* 0.05 frame-num)))) (+ 1 (* 0.3 (sin (* 0.2 frame-num)))) ) (:move-post matrix (* 0.7 (sin (* 0.05 frame-num))) (* 0.7 (sin (* 0.056 frame-num))) (* 0.7 (sin (* 0.1 frame-num))) ) )))
To see toggling the color of the sphere, selecting/deselecting of the sphere and per frame movement and scaling, click on the link: demo2 on an SGI machine in biostructure, or save and run it locally from your hard drive. To run, type: skandha4 sphere-demo2.xS4 from the subdirectory where you saved the file.