About Color Animation
Volume Number: | | 6
|
Issue Number: | | 5
|
Column Tag: | | Pascal Procedures
|
Related Info: Vert. Retrace Mgr Event Manager Color Quickdraw
Color Animation
By Ajay Nath, Oakland Gardens, NY
Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.
[Ajay Nath has B.A. degrees in Mathematics and Chemistry from New York University and is currently a third year medical student at the Mount Sinai School of Medicine where he tries to find the time to program and to learn medicine.]
It all started when I started playing with a shareware breakout game. Paul, a friend of mine who saw me playing it, thought it was neat and liked playing it but there were a few problems with the game. It was an old game and didnt use the larger screen of my Mac IIcx or color and it had other annoying things about it. Well Ive been much too busy to write a new game for my friend, but I was able to discover a method for doing simple animation that will work well on a Mac II or better machine.
When I first sat down and thought about the problem I thought, well Ill simply use a vbl task to set a global variable and my programs main loop will examine that variable and know when to draw. To do this I declared a variable of the following simple type:
{1}
XVBLRec= RECORD
xVBLTask : VBLTask;
xDoTask: INTEGER;
END;
The first field of the record (xVBLTask) is used in the calls to install and remove the vbl task, the second field xDoTask is an integer which the vbl task I use will set to 1 when it runs. My programs main loop just has to examine the xDoTask field and draw when it is non-zero.
This technique is not very different from the method which involves examining the current value of the Mac global variable TickCount and drawing when it changes, i.e. (in pseudocode):
{2}
count = TickCount;
while count = TickCount do nothing;
{ start drawing real fast! }.
On older Macs the TickCount was changed when vbl tasks were run so the above method was really very similar to using a vbl task to set a global variable.
I took a quick look in Inside Mac Vol. V (pp. 566-567) before I started writing my vbl task and found some interesting information there. The new Macs have vertical retrace interrupts which can vary according to their video card and we can create vbl tasks which run at the heart rate of the whichever video card we want. Well, the obvious thing to do is to run our vbl task at the rate of the video card of our main screen, and we then should be able to do simple animation easily! The new calls are called SlotVInstall and SlotVRemove and just like the old calls need a pointer to a vbltask record but in addition, need a slot number so they know which slot to attach the vbl task to. All we need to know then is the slot number of the video card which drives our main screen. After a quick five minute search through IM V I found a call, GetVideoDefault, which would give me the slot number of the default video card and I was on my way.
I used TML Pascal II to write my main program and used MPW Assembler to write the vbl task. Ill explain the vbl tasks code first since its so simple (only three lines). When the task runs, register A0 is a ptr to our vbltask record and this allows us to reset our tasks vblcount. Since we defined an integer that comes right after this record in our XVBLRec type (see above), we can change its value to 1 at this point since A0 points to our record. The three lines of the vbl task do the following (in pseudocode):
1) reset the vblcount
2) set the xDoTask variable to 1
3) return (exit) from the vbl task.
All the main program loop has to do is (in pseudocode):
repeat
if (xDoTask <> 0) begin
DrawStuff;
set xDoTask to 0
endif
until done
The following MPW commands will build and run the example program (you may need to change the paths to build it on your hard disk):
# 3
####
Asm vbltask.a
TMLPascal slotvbltaskdemo.p
Link -w -t APPL -c ????
slotvbltaskdemo.p.o vbltask.a.o
HD-80:MPW:Libraries:Libraries:Runtime.o
HD-80:MPW:Libraries:Libraries:Interface.o
HD-80:MPW:Libraries:TMLPLibraries:TMLPasLib.o
HD-80:MPW:Libraries:TMLPLibraries:SANELib.o
-o SlotVBLTaskDemo
Rez -append -o SlotVBLTaskDemo slotvbltaskdemo.r
SlotVBLTaskDemo
####
Note that the code does check to make sure that the machine its running on is a Mac II or better before running, since the new calls are not available on lower end machines. On lower machines using regular vbl tasks is probably sufficient to do simple animation. Thats about all the explanation the code needs other than to say that it bounces a red string vertically in its draggable window and can run in the background.
There is a bug which occurs when things are being drawn at the top of the screen and when you use the simple method of erase old stuff then draw new stuff as I do in this example. You may see things start to flicker when drawing at the top of the screen. The problem I think is in the way the screen is drawn which is left to right and from top to bottom. I havent thought of a solution for this problem yet other than to use a call to CopyBits to blast in whatever your drawing, rather then to use straight QuickDraw calls. If anyone can think of a good explanation of this problem and how to solve it Id like to hear about it.
Much love and thanks to the boys of 9A and of course, to K.S.
Listing: vbltask.a
INCLUDESysEqu.a
EXPORT MyVBLTask
MyVBLTask PROC
;
; On entry A0 is a ptr to our XVBLRec which was defined
; as follows:
;
;XVBLRec= RECORD
;xVBLTask : VBLTask;
;xDoTask: INTEGER;
;END;
;
; We can use MPW RECORDs to define it in assembly as follows:
;
XVBLRec RECORD 0
xVBLTaskDS.BvblPhase+2
xDoTask DS.W1
ENDR
kVBLCount EQU 1
WITH XVBLRec
; Reset the vblCount
MOVE.W #kVBLCount,XVBLRec+xVBLTask+vblCount(A0)
; Make xDoTask non-zero
MOVE.W #1,XVBLRec+xDoTask(A0)
; Exit Task
RTS
ENDWITH
ENDP
END ; For Assembler
Listing: slotvbltaskdemo.p
PROGRAM SlotVBLTaskDemo;
USES
MemTypes, QuickDraw, OSIntf, ToolIntf;
{ Declare our external vbl task }
PROCEDURE MyVBLTask; EXTERNAL;
CONST
F = False;
T = True;
kBallSpeed = 4;
TYPE
XVBLRec= RECORD
xVBLTask : VBLTask;
xDoTask: INTEGER;
END;
VAR
gDone : BOOLEAN;
gVideoInfoRec : DefVideoRec;
gEvt : EventRecord;
gFontInfo: FontInfo;
gBoxDir: INTEGER;
gBoxRect : Rect;
gBoxColor: RGBColor;
gBoxString : Str255;
gDragRect,
gRect : Rect;
gMainWindow: WindowPtr;
gWRec : WindowRecord;
gXVBLRec : XVBLRec;
{ Proc to run if a crash occurs }
PROCEDURE Crash;
BEGIN
ExitToShell;
END;
{ Proc that does MacInits }
PROCEDURE MacInits;
BEGIN
MaxApplZone;
MoreMasters;
MoreMasters;
MoreMasters;
MoreMasters;
InitGraf (@thePort);
InitFonts;
InitWindows;
InitMenus;
TEInit;
InitDialogs (@Crash);
InitCursor;
END;
{ Proc that sets up our window }
PROCEDURE SetUpWindows;
BEGIN
gRect := ScreenBits.bounds;
WITH gRect DO BEGIN
left := left + 20;
right := left + 400;
top := top + 40;
bottom := bottom - 20;
END;
gMainWindow := NewCWindow (@gWRec, gRect,
SlotVBLTaskDemo - Click in this window to Exit,
T, noGrowDocProc, WindowPtr (-1), F, 0);
SetPort (gMainWindow);
END;
{ Proc that sets up the box for our ball }
PROCEDURE SetUpBox;
VAR
width : INTEGER;
BEGIN
gBoxDir := kBallSpeed;
gBoxString := I love you Manu;
width := StringWidth (gBoxString);
GetFontInfo (gFontInfo);
WITH gBoxRect DO BEGIN
top := 10;
bottom := top + gFontInfo.ascent + gFontInfo.descent;
left := ((gRect.right - gRect.left) DIV 2) - (width DIV 2);
right := left + width;
END;
WITH gBoxColor DO BEGIN
red := -1;
green := 0;
blue := 0;
END;
RGBForeColor (gBoxColor);
END;
{ Proc that inits our globals }
PROCEDURE GlobalInits;
BEGIN
SetUpWindows;
SetUpBox;
gDragRect := ScreenBits.bounds;
InsetRect (gDragRect, 4, 4);
gDone := F;
END;
{ Proc that cleans up before we exit }
PROCEDURE CleanUps;
BEGIN
CloseWindow (gMainWindow);
END;
{ Func that makes sure we run in the currect environment }
FUNCTION EnvironmentOK : BOOLEAN;
VAR
err : OSErr;
theWorld : SysEnvRec;
BEGIN
EnvironmentOK := F; { Assume env is bad }
err := SysEnvirons (curSysEnvVers, theWorld);
IF (err = noErr) THEN BEGIN
IF (theWorld.machineType >= envMacII) THEN
EnvironmentOK := T;
END; { IF }
END;
{ Func that sets up our vbl task }
FUNCTION VBLTaskSetUp : BOOLEAN;
VAR
err : OSErr;
BEGIN
VBLTaskSetUp := F; { Assume we fail }
GetVideoDefault (@gVideoInfoRec);
WITH gXVBLRec DO BEGIN
xDoTask := 0;
WITH xVBLTask DO BEGIN
qType := ORD (vType);
vblAddr := @MyVBLTask;
vblCount := 1;
vblPhase := 0;
END;
err := SlotVInstall (@xVBLTask, gVideoInfoRec.sdSlot);
IF (err = noErr) THEN
VBLTaskSetUp := T; { We succeeded! }
END;
END;
{ Proc that removes our vbl task }
PROCEDURE RemoveVBLTask;
VAR
err : OSErr;
BEGIN
err := SlotVRemove (@gXVBLRec.xVBLTask, gVideoInfoRec.sdSlot);
END;
{ Proc that draws our window }
PROCEDURE DrawStuff;
VAR
oldPort: GrafPtr;
BEGIN
GetPort (oldPort);
SetPort (gMainWindow);
EraseRect (gBoxRect);
WITH gBoxRect DO BEGIN
top := top + gBoxDir;
bottom := bottom + gBoxDir;
END;
IF (gBoxRect.bottom >= gWRec.port.portRect.bottom) THEN
gBoxDir := -kBallSpeed
ELSE BEGIN
IF (gBoxRect.top <= gWRec.port.portRect.top) THEN
gBoxDir := kBallSpeed;
END;
MoveTo (gBoxRect.left, gBoxRect.bottom - gFontInfo.descent);
DrawString (gBoxString);
SetPort (oldPort);
END;
{ Proc that handles mouse downs }
PROCEDURE DoMouseDown;
VAR
result : INTEGER;
whichWindow: WindowPtr;
BEGIN
result := FindWindow (gEvt.where, whichWindow);
CASE result OF
inContent:
gDone := T;
inDrag:
DragWindow (whichWindow, gEvt.where, gDragRect);
OTHERWISE
END;
END;
{ Main }
BEGIN
MacInits;
IF (EnvironmentOK) THEN BEGIN
GlobalInits;
IF (VBLTaskSetUp) THEN BEGIN
REPEAT
IF NOT (WaitNextEvent (mDownMask, gEvt, 0, NIL)) THEN BEGIN
IF (gXVBLRec.xDoTask <> 0) THEN BEGIN
DrawStuff;
gXVBLRec.xDoTask := 0;
END;
END
ELSE
DoMouseDown;
UNTIL (gDone);
RemoveVBLTask;
END;
CleanUps;
END;
ExitToShell; { Back to the Finder! (or MultiFinder) }
END.
Listing: slotvbltaskdemo.r
#include Types.r
#define kMinSize 25/* applications minimum size (in K) */
#define kPrefSize50/* applications preferred size (in K) */
resource SIZE (-1) {
dontSaveScreen,
acceptSuspendResumeEvents,
enableOptionSwitch,
canBackground, /* we can background */
multiFinderAware,
backgroundAndForeground,
dontGetFrontClicks,
ignoreChildDiedEvents,
not32BitCompatible,
reserved,
reserved,
reserved,
reserved,
reserved,
reserved,
reserved,
kPrefSize * 1024,
kMinSize * 1024
};