Feedback Search Top Backward
EDM/2

Sprites and Animation - Part 2

Written by Larry Salomon, Jr

 
Part 1 Part 3 Part 4

Introduction

Last month, we took a good look at the beginnings of a sprite library that is well-suited for simple animation. We looked at:

  • how a sprite is drawn
  • how the drawing of sprites requires a mask, which adds an extra burden on the management of the sprite data structure by the library
  • how a sprite is moved in a manner that minimizes flicker
But even discussing these things left some holes, namely the background and the workarea that enables the flicker-free movement. This month, we will wrap up our discussion on the design of the library and will begin delving into the code for the sprite library itself. Next month's conclusion will look into the intricacies of animating a set of sprites using the i495.exe sample as a starting point.

Ch-Ch-Ch-Changes

Ah, there's nothing like quintessential Bowie...

Since last month, I have integrated the sprite library into the Common/2 library that was on hobbes (if this new version isn't there already, wait for a week and remind me if I still have forgotten). However, I have kept the original version and have modified it to provide semaphore exclusion to the data structures (actually, I surgically cut-n-pasted the related functions from Common/2 <grin>), and it is this version that we will present in this series. It should be noted, though, that the version in Common/2 does come complete with an online reference.

Monkey Bars and the Like

A distinguishing characteristic between a sprite and a bitmap is that the former has a transparency color which means that whatever is below the sprite shows through it. If the background were simply a solid color, this could be done through sleight-of-hand by drawing the bitmap with the background color in the areas that are to be see-through. Movement would be quite simple, reduced to a few WinFillRect() calls and a single GpiWCBitBlt().

Unfortunately, a sprite can make no assumptions about the underlying surface: it could be a solid color or it could be a complex drawing or it even could be another sprite. Worse yet, we cannot simply take what is already on the screen, for what shall we restore there if - for example - the sprite has its visibility state set to FALSE?

Because of this (and other reasons), we need to maintain yet another data structure which will, among other things, manage a static bitmap to be used for the background; we will call it a playground since it is the area in which we will let the sprites play. Conceptually, a playground is the "blackboard" while each sprite is just a chalk drawing on the blackboard, so all drawing related functions are considered to be associated with a playground instead of the sprites that occupy a playground.

The playground support must provide the following functions:

  • Create a playground
  • Destroy a playground
  • Add a sprite to the playground
  • Remove a sprite from the playground
  • Draw the entire playground
Adding and removing sprites from the playground is needed so that we can access data structures in the playground given the sprite handle. For example, only one work area is needed per playground since one thread at most can access an HPS, so we put the work area in the playground, but need to access this work area from each sprite.

Drawing a playground involves drawing the background and then looping through its membership list to draw each sprite at its current position. This is intended to provide a simple method of processing the WM_PAINT message within an application.


If all of these design issues get confusing, remember that I have the power of hindsight and you do not, so do not get dismayed or (worse yet) think that I have some divine capability to think of these things before writing a line of code; I had the same trial-n-error development process that you would have were our places exchanged.

Final Design Considerations

In even the most elementary computer programming classes, the concept of code reuse is hammered into the brains of the students; here, this concept is stressed as well. There are many code blocks that are needed in various places throughout the library, so these code blocks were turned into functions.

Mutually exclusive access to the data structures is also an important part of any good library, and it is implemented here through the use of a mutex sempahore and a status flag; the status flag indicates if access has already been granted to a function, i.e. if a function calls another function, the called function will deadlock if it requests the semaphore. The access-granting function is accessSem(), and it would be worth the time to understand how this is implemented to avoid confusion later.

Where's Your Library Card?

Since utilitizing the library involves far more complex coding than the code for the library itself (did I ever say that animation was easy?), we will look at the library itself. If you have not already done so, unzip the source.zip file and take a look at the files within.

MAKEFILE
the makefile for the library
SPRITE.C
the source file
SPRITE.HPP
the .H preprocessor file. This is fed to HEADER.EXE which generates an implementor's .H file and a user's .H file. You should change the first two lines of this file so that the paths make sense.
//@HEADER OUTFILE INT D:\SOURCE\SPRITE\SPRITE.H
//@HEADER OUTFILE EXT D:\MYINC\SPRITE.H
The INT line defines the file name of the "internal" use .H file, while the EXT line defines the file name of the "external" use .H file.
SPRITE.H
Generated by the command "HEADER -tINT SPRITE.HPP"
SPRITE.OBJ
Object file
SPRITE.LIB
Library file
HEADER.EXE
Header file preprocessor. I think I have placed this on hobbes, possibly under the name HPREP. If not, this is an older version, but it will do. (I keep forgetting to bring the newer version in to work.)
Before we can begin to understand the code, we must first understand the data structures on which the code operates, so we first look at the playground:

typedef struct _PLAYGROUND {
   ULONG ulSig;
   ULONG ulStatus;
   HMTX hsmAccess;

   HAB habAnchor;
   HDC hdcWork;
   HPS hpsWork;
   HBITMAP hbmWork;

   HBITMAP hbmBack;
   BITMAPINFOHEADER2 bmihBack;
   LONG lBackColor;

   BOOL bUpdate;

   HSPRITE ahsSprites[MAX_SPRITES];
   ULONG ulNumMembers;
} PLAYGROUND, *HPLAYGROUND;
typedef HPLAYGROUND *PHPLAYGROUND;
ulSig
4-byte signature for the data structure, used for parameter validation.
ulStatus
32-bit status flags used for semaphore access at this time.
hsmAccess
mutex (mutual exclusion) semaphore handle.
habAnchor
anchor block handle of the calling thread.
hdcWork
OD_MEMORY device context handle for the work area.
hpsWork
presentation space handle associated with hdcWork.
hbmWork
bitmap handle set into hpsWork.
hbmBack
bitmap handle for the background bitmap. If NULLHANDLE, the playground has a background color instead (lBackColor).
bmihBack
bitmap information header for hbmBack. If hbmBack is NULLHANDLE, the cx and cy fields indicate the size of the playground; otherwise the size of the playground is specified by the size of the background bitmap.
lBackColor
the background color of the playground, if hbmBack is NULLHANDLE.
bUpdate
update flag. If FALSE, no drawing is actually performed. This is useful for changing sprites in place.
ahsSprites
array of sprite handles comprising the membership list.
ulNumMembers
current number of members.
We will see how these fields are used when we look at the code. But now, we will look at the sprite structure:

typedef struct _SPRITE {
   ULONG ulSig;
   ULONG ulStatus;
   HMTX hsmAccess;

   HAB habAnchor;
   HBITMAP hbmBitmap;
   HBITMAP hbmMask;
   BITMAPINFOHEADER2 bmihBitmap;
   BITMAPINFOHEADER2 bmihMask;

   struct _PLAYGROUND *hpgPlay;
   POINTL ptlPos;
   BOOL bVisible;
} SPRITE, *HSPRITE;
typedef HSPRITE *PHSPRITE;
ulSig
4-byte signature for the data structure, used for parameter validation.
ulStatus
32-bit status flags used for semaphore access at this time.
hsmAccess
mutex (mutual exclusion) semaphore handle.
habAnchor
anchor block handle of the calling thread.
hbmBitmap
bitmap handle which defines the sprite.
hbmMask
bitmap handle which defines the mask.
bmihBitmap
bitmap information header for hbmBitmap.
bmihMask
bitmap information header for hbmMask.
hpgPlay
playground handle of which the sprite is a member.
ptlPos
current position.
bVisible
current visibility state.
A comment on the list of exposed functions below: notice the symmetry of the function names. For each create, there is a destroy; querys have corresponding sets when appropriate; the add has a remove. While much is often said about intuitiveness of the user-interface, the same concepts along with the advantages gained can be applied to the "programmer-interface". The non-exposed (internal) functions, obviously, do not need to follow this guideline, although it does help if more than one person is developing and/or maintaining the code.

Each function below is a hypertext link to its code and the explanation of the code; feel free to explore the functions in any order.

Internal Functions

External Functions

Summary

This month, we finished our discussion of the design of the sprite library by describing the need for a master data structure called the playground, and the code to implement the library routines was presented to illustrate the concepts that we have already learned in our discussions up to this point. Next month, we will take these underpinings and will apply them to the i495.exe application to see how they can be utililized to perform rudimentary animation.

All comments, suggestions, bugs, etc. are welcome c/o the author.

Part 1 Top Part 3 Part 4

Functions code and Explanations

accessSem
The purpose of this function is to provide mutually exclusive access to the data structures used by the library. By requiring that all data structures have a common set of fields defined first (semaphore handle, etc.), we can cast the data structures to the type PHANDLE to allow us access to the common fields.


static USHORT accessSem(PHEADER phHandle,USHORT usAction)
//-------------------------------------------------------------------------
// This function provides semaphore access for mutual exclusion of private
// data access.
//
// Input:  phHandle - points to the handle header
//         usAction - specifies the action to perform:
//            ACCSEM_SET - requests access to the handle
//            ACCSEM_CLEAR - relinquishes access to the handle
//            ACCSEM_ALREADYSET - not used
//            ACCSEM_NOTSET - not used
// Returns:  ACCSEM_ERROR if an error occurred, else the action to take
//           on the next call to this function
//-------------------------------------------------------------------------
{
   switch (usAction) {
   case ACCSEM_SET:
      if ((phHandle->ulStatus & HSTATUS_INLIBRARY)!=0) {
         return ACCSEM_ALREADYSET;
      } /* endif */

      DosRequestMutexSem(phHandle->hsmAccess,SEM_INDEFINITE_WAIT);
      phHandle->ulStatus|=HSTATUS_INLIBRARY;
      return ACCSEM_CLEAR;
   case ACCSEM_CLEAR:
      if ((phHandle->ulStatus & HSTATUS_INLIBRARY)==0) {
         return ACCSEM_NOTSET;
      } /* endif */

      DosReleaseMutexSem(phHandle->hsmAccess);
      phHandle->ulStatus&=~HSTATUS_INLIBRARY;
      return ACCSEM_SET;
   case ACCSEM_ALREADYSET:
      return ACCSEM_NOTSET;
   case ACCSEM_NOTSET:
      return ACCSEM_ALREADYSET;
   default:
      return ACCSEM_ERROR;
   } /* endswitch */
}
Back

clipBltPoints
The library clips all drawing to the playground size. (A future enhancement to the library would be to allow the application to specify where in a window the lower-left corner of the playground should be.) This function clips a pair of rectangles (which are defined by a pair of points, meaning that we have four points total) to a rectangle defined to be (0,0)-(pszlPlay->cx,pszlPlay->cy).

You can see the clipping effect by enlarging the size of the i495 window.


static BOOL clipBltPoints(HAB habAnchor,PPOINTL pptlArray,PSIZEL pszlPlay)
//-------------------------------------------------------------------------
// This function clips the first two points in pptlArray to a rectangle
// of size pszlPlay.  The last two points in pptlArray are then adjusted
// by the amount clipped.
//
// It is assumed that the first two points refer to a coordinate space
// of size pszlPlay and that the two rectangles formed by the first and
// last pair of points in pptlArray are of the same size.
//
// Input:  habAnchor - anchor block of the calling thread.
//         pptlArray - points to array of 4 points for GpiBitBlt()
//         pszlPlay - points to the size of the playground to clip to
// Output:  pptlArray - points to adjusted array
// Returns:  TRUE if at least one pel was *not* clipped, FALSE if all
//           points fell outside of the clipping region.
//-------------------------------------------------------------------------
{
   RECTL rclPlay;
   RECTL rclDest;
   RECTL rclInter;
   RECTL rclDelta;

   rclPlay.xLeft=0;
   rclPlay.yBottom=0;
   rclPlay.xRight=pszlPlay->cx-1;
   rclPlay.yTop=pszlPlay->cy-1;

   rclDest.xLeft=pptlArray[0].x;
   rclDest.yBottom=pptlArray[0].y;
   rclDest.xRight=pptlArray[1].x;
   rclDest.yTop=pptlArray[1].y;

   WinIntersectRect(habAnchor,&rclInter,&rclPlay,&rclDest);

   //----------------------------------------------------------------------
   // If the result is an empty rectangle, return FALSE to indicate so.
   //----------------------------------------------------------------------
   if (WinIsRectEmpty(habAnchor,&rclInter)) {
      return FALSE;
   } /* endif */

   rclDelta.xLeft=rclDest.xLeft-rclInter.xLeft;
   rclDelta.yBottom=rclDest.yBottom-rclInter.yBottom;
   rclDelta.xRight=rclDest.xRight-rclInter.xRight;
   rclDelta.yTop=rclDest.yTop-rclInter.yTop;

   pptlArray[0].x-=rclDelta.xLeft;
   pptlArray[0].y-=rclDelta.yBottom;
   pptlArray[1].x-=rclDelta.xRight;
   pptlArray[1].y-=rclDelta.yTop;
   pptlArray[2].x-=rclDelta.xLeft;
   pptlArray[2].y-=rclDelta.yBottom;
   pptlArray[3].x-=rclDelta.xRight;
   pptlArray[3].y-=rclDelta.yTop;
   return TRUE;
}
Back

drawSpriteAt
This function draws a sprite at the specified position; it calls clipBltPoints() to insure that all necessary clipping is performed. An advantage of having this as a function is that the transition to z-order implementation is eased somewhat. It is because of this that the background is not drawn in this function, but is removed to its own function - drawBackAt().


static BOOL drawSpriteAt(HPS hpsDraw,
                         HSPRITE hsSprite,
                         PSIZEL pszlSize,
                         PPOINTL pptlPos)
//-------------------------------------------------------------------------
// This function draws the sprite at the specified position.  It is assumed
// that the background has already been drawn into hpsDraw before this
// function is called.
//
// Input:  hpsDraw - handle of the presentation space to draw in
//         hsSprite - handle of the sprite to draw
//         pszlSize - points to the size of hpsDraw.  If NULL, the size
//                    of the playground is used.
//         pptlPos - points to the point specifying the position.  If
//                   NULL, the sprite's current position is used.
// Returns:  TRUE if successful, FALSE otherwise
//-------------------------------------------------------------------------
{
   POINTL ptlUse;
   SIZEL szlUse;
   POINTL aptlPoints[4];

   if (!hsSprite->hpgPlay->bUpdate) {
      return TRUE;
   } /* endif */

   //----------------------------------------------------------------------
   // Initialize the local variables with either what was passed in or
   // the defaults as noted above in the function prologue
   //----------------------------------------------------------------------
   if (pptlPos==NULL) {
      ptlUse=hsSprite->ptlPos;
   } else {
      ptlUse=*pptlPos;
   } /* endif */

   if (pszlSize==NULL) {
      SprQueryPlaygroundSize(hsSprite->hpgPlay,&szlUse);
   } else {
      szlUse=*pszlSize;
   } /* endif */

   aptlPoints[0].x=ptlUse.x;
   aptlPoints[0].y=ptlUse.y;
   aptlPoints[1].x=aptlPoints[0].x+hsSprite->bmihMask.cx-1;
   aptlPoints[1].y=aptlPoints[0].y+hsSprite->bmihMask.cy-1;
   aptlPoints[2].x=0;
   aptlPoints[2].y=0;
   aptlPoints[3].x=aptlPoints[2].x+hsSprite->bmihMask.cx;
   aptlPoints[3].y=aptlPoints[2].y+hsSprite->bmihMask.cy;

   if (clipBltPoints(hsSprite->habAnchor,aptlPoints,&szlUse)) {
      //-------------------------------------------------------------------
      // Blit the mask and then the bitmap
      //-------------------------------------------------------------------
      GpiWCBitBlt(hpsDraw,
                  hsSprite->hbmMask,
                  4,
                  aptlPoints,
                  ROP_SRCAND,
                  BBO_IGNORE);

      GpiWCBitBlt(hpsDraw,
                  hsSprite->hbmBitmap,
                  4,
                  aptlPoints,
                  ROP_SRCPAINT,
                  BBO_IGNORE);
   } /* endif */

   return TRUE;
}
Back

drawBackAt
This function draws the background at the specified position with the specified size. This is used to copy the background onto the display as well as into the work area as needed. Notice that this is where background coloring is done if a background bitmap was not specified.


static BOOL drawBackAt(HPS hpsDraw,
                       HPLAYGROUND hpgPlay,
                       PRECTL prclDest,
                       PSIZEL pszlDest,
                       PRECTL prclSrc)
//-------------------------------------------------------------------------
// This function draws the background in the specified presentation space.
//
// Input:  hpsDraw - handle of the presentation space to draw in
//         hpgPlay - handle of the playground containing the background
//         prclDest - points to the destination rectangle.  If NULL, the
//                    value of prclSrc is used.
//         pszlDest - points to the size of hpsDraw.  If NULL, the size of
//                    the playground is used.
//         prclSrc - points to the source rectangle.  If NULL, the entire
//                   background is painted.
// Returns:  TRUE if successful, FALSE otherwise
//-------------------------------------------------------------------------
{
   RECTL rclUseSrc;
   RECTL rclUseDest;
   SIZEL szlUse;
   POINTL aptlPoints[4];

   if (!hpgPlay->bUpdate) {
      return TRUE;
   } /* endif */

   if (prclSrc==NULL) {
      rclUseSrc.xLeft=0;
      rclUseSrc.yBottom=0;
      rclUseSrc.xRight=hpgPlay->bmihBack.cx;
      rclUseSrc.yTop=hpgPlay->bmihBack.cy;
   } else {
      rclUseSrc=*prclSrc;
   } /* endif */

   if (prclDest==NULL) {
      rclUseDest=rclUseSrc;
      rclUseDest.xRight--;
      rclUseDest.yTop--;
   } else {
      rclUseDest=*prclDest;
   } /* endif */

   if (pszlDest==NULL) {
      szlUse.cx=hpgPlay->bmihBack.cx;
      szlUse.cy=hpgPlay->bmihBack.cy;
   } else {
      szlUse=*pszlDest;
   } /* endif */

   aptlPoints[0].x=rclUseDest.xLeft;
   aptlPoints[0].y=rclUseDest.yBottom;
   aptlPoints[1].x=rclUseDest.xRight;
   aptlPoints[1].y=rclUseDest.yTop;
   aptlPoints[2].x=rclUseSrc.xLeft;
   aptlPoints[2].y=rclUseSrc.yBottom;
   aptlPoints[3].x=rclUseSrc.xRight;
   aptlPoints[3].y=rclUseSrc.yTop;

   if (clipBltPoints(hpgPlay->habAnchor,aptlPoints,&szlUse)) {
      //-------------------------------------------------------------------
      // If there is a background bitmap, blit it, otherwise black out the
      // area.
      //-------------------------------------------------------------------
      if (hpgPlay->hbmBack!=NULLHANDLE) {
         GpiWCBitBlt(hpsDraw,
                     hpgPlay->hbmBack,
                     4,
                     aptlPoints,
                     ROP_SRCCOPY,
                     BBO_IGNORE);
      } else {
         //----------------------------------------------------------------
         // WinFillRect() excludes the top and right of the rectangle
         //----------------------------------------------------------------
         rclUseDest.xRight++;
         rclUseDest.yTop++;
         WinFillRect(hpsDraw,&rclUseDest,hpgPlay->lBackColor);
      } /* endif */
   } /* endif */

   return TRUE;
}
Back

getMemHps
There is nothing special about this function that needs to be noted.


static HPS getMemHps(HAB habAnchor)
//-------------------------------------------------------------------------
// This function creates an HPS associated with a memory HDC.  The
// HDC handle can be retrieved using the GpiQueryDevice() function.
//
// Input:  habAnchor - anchor block of the calling thread.
// Returns:  HPS handle if successful, NULLHANDLE otherwise
//-------------------------------------------------------------------------
{
   HDC hdcMem;
   SIZEL szlHps;
   HPS hpsMem;

   hdcMem=DevOpenDC(habAnchor,OD_MEMORY,"*",0,NULL,NULLHANDLE);
   if (hdcMem==NULLHANDLE) {
      return NULLHANDLE;
   } /* endif */

   szlHps.cx=0;
   szlHps.cy=0;

   hpsMem=GpiCreatePS(habAnchor,
                      hdcMem,
                      &szlHps,
                      PU_PELS|GPIT_MICRO|GPIA_ASSOC);
   if (hpsMem==NULLHANDLE) {
      DevCloseDC(hdcMem);
   } /* endif */

   return hpsMem;
}
Back

queryHandleType
The purpose of this function is parameter validation, and uses the ulSig field of both data structures. Note that for non-accessible memory pointers, this function will still cause the application to trap (although it is possible to check for NULL).


static ULONG queryHandleType(PVOID pvHandle)
//-------------------------------------------------------------------------
// This function returns a QH_* constant specifying the handle type.  It
// will be replaced by CmnQueryHandle() when this subsystem is integrated
// into Common/2.
//
// Input:  pvHandle - points the the handle to query
// Returns:   QH_ERROR if error, QH_* constant otherwise
//-------------------------------------------------------------------------
{
   if (pvHandle==NULL) {
      return QH_ERROR;
   } /* endif */

   switch (((PHEADER)pvHandle)->ulSig) {
   case SIG_HSPRITE:
      return QH_HSPRITE;
   case SIG_HPLAYGROUND:
      return QH_HPLAYGROUND;
   default:
      return QH_ERROR;
   } /* endswitch */
}
Back

SprAddSprite
This function adds a sprite handle to the end of the ahsSprites array within the HPLAYGROUND structure. By insuring - in SprRemoveSprite() - that the array is always compact, we save a test for NULL in the SprDrawPlayground() loop. Note that there are no functions for modifying the order of the sprite within the array, because as of now there is no need. However, once z-ordering is added to the library, another function - SprSetLayer() - will be added to set the sprite's position to the top, bottom, previous, or next position in the z-order stack.


SPRERROR EXPENTRY SprAddSprite(HPLAYGROUND hpgPlay,HSPRITE hsSprite)
//-------------------------------------------------------------------------
// This function labels a sprite as a "member" of the specified playground.
// Doing so allows the application to control the sprite's position,
// visibility, etc. on a drawing surface.
//
// Input:  hpgPlay - handle to the playground
//         hsSprite - handle to the sprite to add
// Returns:  SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise
//-------------------------------------------------------------------------
{
   USHORT usAction;

   if (queryHandleType(hpgPlay)!=QH_HPLAYGROUND) {
      return SPR_ERR_BADHANDLE;
   } else
   if (queryHandleType(hsSprite)!=QH_HSPRITE) {
      return SPR_ERR_BADHANDLE;
   } /* endif */

   usAction=accessSem((PHEADER)hpgPlay,ACCSEM_SET);

   if (hsSprite->hpgPlay!=NULL) {
      accessSem((PHEADER)hpgPlay,usAction);
      return SPR_ERR_HASPLAYGROUND;
   } else
   if (hpgPlay->ulNumMembers==MAX_SPRITES) {
      accessSem((PHEADER)hpgPlay,usAction);
      return SPR_ERR_PLAYGROUNDFULL;
   } /* endif */

   hpgPlay->ahsSprites[hpgPlay->ulNumMembers]=hsSprite;
   hpgPlay->ulNumMembers++;

   hsSprite->hpgPlay=hpgPlay;

   accessSem((PHEADER)hpgPlay,usAction);
   return SPR_ERR_NOERROR;
}
Back

SprCreatePlayground
Note the use of calloc(). Using any of the C runtime is not a good thing because it is implemented (usually) using suballocation, meaning an application can accidentally increment hpgPlay by 2 bytes (for example) and would not get a trap notification by OS/2; instead unpredictable behavior would occur, which misleads the developer into thinking a stack error is occuring. It would be better to use DosAllocMem() instead.


SPRERROR EXPENTRY SprCreatePlayground(HAB habAnchor,PHPLAYGROUND phpgPlay)
//-------------------------------------------------------------------------
// This function creates a playground to which sprites can be added.
//
// Input:  habAnchor - anchor block of the calling thread.
// Output:  phpgPlay - points to the variable with the HPLAYGROUND handle
// Returns:  SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise
//-------------------------------------------------------------------------
{
   BITMAPINFOHEADER2 bmihInfo;
   LONG lValue;

   *phpgPlay=calloc(1,sizeof(PLAYGROUND));
   if (*phpgPlay==NULL) {
      *phpgPlay=NULL;
      return SPR_ERR_NOMEMORY;
   } /* endif */

   (*phpgPlay)->ulSig=SIG_HPLAYGROUND;
   (*phpgPlay)->ulStatus=0;

   if (DosCreateMutexSem(NULL,&(*phpgPlay)->hsmAccess,0,FALSE)) {
      free(*phpgPlay);
      return SPR_ERR_RESOURCE;
   } /* endif */

   (*phpgPlay)->habAnchor=habAnchor;

   (*phpgPlay)->hpsWork=getMemHps(habAnchor);
   if ((*phpgPlay)->hpsWork==NULLHANDLE) {
      free(*phpgPlay);
      *phpgPlay=NULL;
      return SPR_ERR_RESOURCE;
   } /* endif */

   (*phpgPlay)->hdcWork=GpiQueryDevice((*phpgPlay)->hpsWork);
Since the workarea is used for moving sprites with position overlap, the size of the bitmap to be set in the HPS is (MAX_SPRITE_CX*2,MAX_SPRITE_CY*2).

   bmihInfo.cbFix=16;
   bmihInfo.cx=MAX_SPRITE_CX*2;
   bmihInfo.cy=MAX_SPRITE_CY*2;
   bmihInfo.cPlanes=1;

   DevQueryCaps((*phpgPlay)->hdcWork,CAPS_COLOR_BITCOUNT,1,&lValue);
   bmihInfo.cBitCount=lValue;

   (*phpgPlay)->hbmWork=GpiCreateBitmap((*phpgPlay)->hpsWork,
                                        &bmihInfo,
                                        0,
                                        NULL,
                                        NULL);
   if ((*phpgPlay)->hbmWork==NULLHANDLE) {
      GpiDestroyPS((*phpgPlay)->hpsWork);
      DevCloseDC((*phpgPlay)->hdcWork);
      free(*phpgPlay);
      *phpgPlay=NULL;
      return SPR_ERR_RESOURCE;
   } /* endif */

   GpiSetBitmap((*phpgPlay)->hpsWork,(*phpgPlay)->hbmWork);

   (*phpgPlay)->lBackColor=CLR_BLACK;
   (*phpgPlay)->bUpdate=TRUE;

   (*phpgPlay)->hbmBack=NULLHANDLE;
   (*phpgPlay)->ulNumMembers=0;
   return SPR_ERR_NOERROR;
}
Back

SprCreateSprite
See SprCreatePlayground() for notes on using calloc().


SPRERROR EXPENTRY SprCreateSprite(HAB habAnchor,
                                  HBITMAP hbmBitmap,
                                  PHSPRITE phsSprite)
//-------------------------------------------------------------------------
// This function creates a sprite from the specified bitmap.  The sprite
// cannot be moved, shown, etc., however, until it is associated with a
// playground.
//
// The color black is used as the transparency color.  If you need to use
// black in the bitmap without it becoming transparent, use the next
// closest color.  <grin>
//
// New sprites are initialized as being at position (0,0) and hidden.
//
// Note that, once this function is called, the bitmap is managed by
// the sprite subsystem.  The bitmap should *NOT* be deleted by the
// application or else unpredictable results will occur.
//
// Input:  habAnchor - anchor block of the calling thread.
//         hbmBitmap - handle to the bitmap
// Output:  phsSprite - points to the sprite handle
// Returns:  SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise
//-------------------------------------------------------------------------
{
   HPS hpsMem;
   HDC hdcMem;
   POINTL aptlPoints[4];

   *phsSprite=calloc(1,sizeof(SPRITE));
   if (*phsSprite==NULL) {
      *phsSprite=NULL;
      return SPR_ERR_NOMEMORY;
   } /* endif */

   (*phsSprite)->ulSig=SIG_HSPRITE;
   (*phsSprite)->ulStatus=0;

   if (DosCreateMutexSem(NULL,&(*phsSprite)->hsmAccess,0,FALSE)) {
      free(*phsSprite);
      return SPR_ERR_RESOURCE;
   } /* endif */

   (*phsSprite)->habAnchor=habAnchor;

   (*phsSprite)->hbmBitmap=hbmBitmap;

   (*phsSprite)->ptlPos.x=0;
   (*phsSprite)->ptlPos.y=0;
   (*phsSprite)->bVisible=FALSE;

   (*phsSprite)->bmihBitmap.cbFix=16;


GpiQueryBitmapInfoHeader((*phsSprite)->hbmBitmap,&(*phsSprite)->bmihBitmap)
;

   //----------------------------------------------------------------------
   // Get an OD_MEMORY HDC and HPS to create the mask in.  Since we will
   // save the bitmap handle, but don't give a $%#@ about the HDC/HPS, they
   // can be local variables.
   //----------------------------------------------------------------------
   hpsMem=getMemHps(habAnchor);
   if (hpsMem==NULLHANDLE) {
      free(*phsSprite);
      *phsSprite=NULL;
      return SPR_ERR_RESOURCE;
   } /* endif */

   hdcMem=GpiQueryDevice(hpsMem);

   (*phsSprite)->bmihMask=(*phsSprite)->bmihBitmap;
   (*phsSprite)->bmihMask.cPlanes=1;
   (*phsSprite)->bmihMask.cBitCount=1;

   (*phsSprite)->hbmMask=GpiCreateBitmap(hpsMem,
                                         &(*phsSprite)->bmihMask,
                                         0,
                                         NULL,
                                         NULL);
   if ((*phsSprite)->hbmMask==NULLHANDLE) {
      GpiDestroyPS(hpsMem);
      DevCloseDC(hdcMem);
      free(*phsSprite);
      *phsSprite=NULL;
      return SPR_ERR_RESOURCE;
   } /* endif */

   GpiSetBitmap(hpsMem,(*phsSprite)->hbmMask);

   aptlPoints[0].x=0;
   aptlPoints[0].y=0;
   aptlPoints[1].x=aptlPoints[0].x+(*phsSprite)->bmihMask.cx-1;
   aptlPoints[1].y=aptlPoints[0].y+(*phsSprite)->bmihMask.cy-1;
   aptlPoints[2].x=0;
   aptlPoints[2].y=0;
   aptlPoints[3].x=aptlPoints[2].x+(*phsSprite)->bmihBitmap.cx;
   aptlPoints[3].y=aptlPoints[2].y+(*phsSprite)->bmihBitmap.cy;

   //----------------------------------------------------------------------
   // Set the foreground to white and the background to black so that this
   // works.  The resulting behavior in the GpiWCBitBlt() call is
   // inconsistent with the docs, so I don't know what to think.
   //----------------------------------------------------------------------
   GpiSetColor(hpsMem,CLR_WHITE);
   GpiSetBackColor(hpsMem,CLR_BLACK);

   GpiWCBitBlt(hpsMem,
               (*phsSprite)->hbmBitmap,
               4,
               aptlPoints,
               ROP_SRCCOPY,
               BBO_IGNORE);
   GpiSetBitmap(hpsMem,NULLHANDLE);
   GpiDestroyPS(hpsMem);
   DevCloseDC(hdcMem);
   return SPR_ERR_NOERROR;
}
Back

SprDestroyPlayground
This function destroys the sprites in the playground before destroying the playground itself. However, if you look in SprDestroySprite() you'll see a check to see if the sprite is still a member of a playground (and fails if it is); thus, we have to call SprRemoveSprite() first.


SPRERROR EXPENTRY SprDestroyPlayground(HPLAYGROUND hpgPlay)
//-------------------------------------------------------------------------
// This function destroys the playground including any sprites that are
// still members of it.  All resources consumed by the playground,
// including the back bitmap, are returned to the system.
//
// Input:  hpgPlay - handle to the playground
// Returns:  SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise
//-------------------------------------------------------------------------
{
   USHORT usAction;
   ULONG ulIndex;
   HSPRITE hsSprite;

   if (queryHandleType(hpgPlay)!=QH_HPLAYGROUND) {
      return SPR_ERR_BADHANDLE;
   } /* endif */

   usAction=accessSem((PHEADER)hpgPlay,ACCSEM_SET);

   if (hpgPlay->hbmBack!=NULLHANDLE) {
      GpiDeleteBitmap(hpgPlay->hbmBack);
   } /* endif */

   for (ulIndex=0; ulIndex<hpgPlay->ulNumMembers; ulIndex++) {
      hsSprite=hpgPlay->ahsSprites[ulIndex];
      SprRemoveSprite(hpgPlay,hsSprite);
      SprDestroySprite(hsSprite);
   } /* endfor */

   GpiSetBitmap(hpgPlay->hpsWork,NULLHANDLE);

   if (hpgPlay->hbmBack!=NULLHANDLE) {
      GpiDeleteBitmap(hpgPlay->hbmBack);
   } /* endif */

   GpiDestroyPS(hpgPlay->hpsWork);
   DevCloseDC(hpgPlay->hdcWork);
   accessSem((PHEADER)hpgPlay,usAction);
   free(hpgPlay);
   return SPR_ERR_NOERROR;
}
Back

SprDestroySprite
There is nothing special about this function that needs to be noted.


SPRERROR EXPENTRY SprDestroySprite(HSPRITE hsSprite)
//-------------------------------------------------------------------------
// This function destroys the sprite and returns all resources to the
// system.
//
// Input:  hsSprite - handle to the sprite
// Returns:  SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise
//-------------------------------------------------------------------------
{
   USHORT usAction;

   if (queryHandleType(hsSprite)!=QH_HSPRITE) {
      return SPR_ERR_BADHANDLE;
   } /* endif */

   usAction=accessSem((PHEADER)hsSprite,ACCSEM_SET);

   if (hsSprite->hpgPlay!=NULL) {
      accessSem((PHEADER)hsSprite,usAction);
      return SPR_ERR_HASPLAYGROUND;
   } /* endif */

   GpiDeleteBitmap(hsSprite->hbmBitmap);
   GpiDeleteBitmap(hsSprite->hbmMask);
   accessSem((PHEADER)hsSprite,usAction);
   free(hsSprite);
   return SPR_ERR_NOERROR;
}
Back

SprDrawPlayground
See SprAddSprite() for notes about ahsSprites being a compact array for performance reasons.


SPRERROR EXPENTRY SprDrawPlayground(HPS hpsDraw,HPLAYGROUND hpgPlay)
//-------------------------------------------------------------------------
// This function redraws the playground and all sprites belonging to the
// playground.
//
// Input:  hpsDraw - handle to the HPS to draw the playground in
//         hpgPlay - handle to the playground
// Returns:  SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise
//-------------------------------------------------------------------------
{
   USHORT usAction;
   ULONG ulIndex;

   if (queryHandleType(hpgPlay)!=QH_HPLAYGROUND) {
      return SPR_ERR_BADHANDLE;
   } /* endif */

   usAction=accessSem((PHEADER)hpgPlay,ACCSEM_SET);

   if (hpgPlay->bUpdate) {
      drawBackAt(hpsDraw,hpgPlay,NULL,NULL,NULL);

      for (ulIndex=0; ulIndex<hpgPlay->ulNumMembers; ulIndex++) {
         SprDrawSprite(hpsDraw,hpgPlay->ahsSprites[ulIndex]);
      } /* endfor */
   } /* endif */

   accessSem((PHEADER)hpgPlay,usAction);
   return SPR_ERR_NOERROR;
}
Back

SprDrawSprite
Given the drawSpriteAt() and drawBackAt() functions, the implementation of this function is fairly trivial.


SPRERROR EXPENTRY SprDrawSprite(HPS hpsDraw,HSPRITE hsSprite)
//-------------------------------------------------------------------------
// This function draws a sprite
//
// Input:  hpsDraw - handle to the HPS to draw the sprite in
//         hsSprite - handle to the sprite
// Returns:  SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise
//-------------------------------------------------------------------------
{
   USHORT usAction;
   RECTL rclSprite;

   if (queryHandleType(hsSprite)!=QH_HSPRITE) {
      return SPR_ERR_BADHANDLE;
   } /* endif */

   usAction=accessSem((PHEADER)hsSprite,ACCSEM_SET);

   if (hsSprite->hpgPlay==NULL) {
      accessSem((PHEADER)hsSprite,usAction);
      return SPR_ERR_HASNOPLAYGROUND;
   } /* endif */

   if ((!hsSprite->bVisible) || (!hsSprite->hpgPlay->bUpdate)) {
      accessSem((PHEADER)hsSprite,usAction);
      return SPR_ERR_NOERROR;
   } /* endif */

   rclSprite.xLeft=hsSprite->ptlPos.x;
   rclSprite.yBottom=hsSprite->ptlPos.y;
   rclSprite.xRight=rclSprite.xLeft+hsSprite->bmihMask.cx;
   rclSprite.yTop=rclSprite.yBottom+hsSprite->bmihMask.cy;

   drawBackAt(hpsDraw,hsSprite->hpgPlay,NULL,NULL,&rclSprite);
   drawSpriteAt(hpsDraw,hsSprite,NULL,NULL);

   accessSem((PHEADER)hsSprite,usAction);
   return SPR_ERR_NOERROR;
}
Back

SprQueryPlaygroundBack
There is nothing special about this function that needs to be noted.


SPRERROR EXPENTRY SprQueryPlaygroundBack(HPLAYGROUND hpgPlay,
                                         HBITMAP *phbmBack)
//-------------------------------------------------------------------------
// This function returns the handle of the background bitmap currently in
// use.
//
// Input:  hpgPlay - handle to the playground
// Output:  phbmBack - points to the handle to the background bitmap.
// Returns:  SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise
//-------------------------------------------------------------------------
{
   USHORT usAction;

   if (queryHandleType(hpgPlay)!=QH_HPLAYGROUND) {
      return SPR_ERR_BADHANDLE;
   } /* endif */

   usAction=accessSem((PHEADER)hpgPlay,ACCSEM_SET);
   *phbmBack=hpgPlay->hbmBack;
   accessSem((PHEADER)hpgPlay,usAction);
   return SPR_ERR_NOERROR;
}
Back

SprQueryPlaygroundColor
There is nothing special about this function that needs to be noted.


SPRERROR EXPENTRY SprQueryPlaygroundColor(HPLAYGROUND hpgPlay,
                                          PLONG plBackColor)
//-------------------------------------------------------------------------
// This function returns the background color of the playground and is
// only valid if the playground doesn't have a bitmap.
//
// Input:  hpgPlay - handle to the playground
//         plBackColor - points to the variable to receive the background
//                       color
// Returns:  SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise
//-------------------------------------------------------------------------
{
   USHORT usAction;

   if (queryHandleType(hpgPlay)!=QH_HPLAYGROUND) {
      return SPR_ERR_BADHANDLE;
   } /* endif */

   usAction=accessSem((PHEADER)hpgPlay,ACCSEM_SET);

   if (hpgPlay->hbmBack!=NULLHANDLE) {
      accessSem((PHEADER)hpgPlay,usAction);
      return SPR_ERR_HASBACKGROUND;
   } /* endif */

   *plBackColor=hpgPlay->lBackColor;
   accessSem((PHEADER)hpgPlay,usAction);
   return SPR_ERR_NOERROR;
}
Back

SprQueryPlaygroundSize
There is nothing special about this function that needs to be noted.


SPRERROR EXPENTRY SprQueryPlaygroundSize(HPLAYGROUND hpgPlay,PSIZEL
pszlSize)

//-------------------------------------------------------------------------
// This function returns the size of the playground.  For playgrounds with
// bitmaps set as the background, the returned value is the size of the
// bitmap.  Otherwise, the returned value is that which was specified on
// the last call to SprSetPlaygroundSize().
//
// Input:  hpgPlay - handle to the playground
//         pszlSize - points to the variable to receive the size of the
//                    playground
// Returns:  SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise
//-------------------------------------------------------------------------
{
   USHORT usAction;

   if (queryHandleType(hpgPlay)!=QH_HPLAYGROUND) {
      return SPR_ERR_BADHANDLE;
   } /* endif */

   usAction=accessSem((PHEADER)hpgPlay,ACCSEM_SET);
   pszlSize->cx=hpgPlay->bmihBack.cx;
   pszlSize->cy=hpgPlay->bmihBack.cy;
   accessSem((PHEADER)hpgPlay,usAction);
   return SPR_ERR_NOERROR;
}
Back

SprQuerySpritePosition
There is nothing special about this function that needs to be noted.


SPRERROR EXPENTRY SprQuerySpritePosition(HSPRITE hsSprite,PPOINTL pptlPos)
//-------------------------------------------------------------------------
// This function returns the current position of the sprite.  Note that
// a sprite has a current position even if it is hidden.
//
// Input:  hsSprite - handle to the sprite
// Output:  pptlPos - points to the current position
// Returns:  SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise
//-------------------------------------------------------------------------
{
   USHORT usAction;

   if (queryHandleType(hsSprite)!=QH_HSPRITE) {
      return SPR_ERR_BADHANDLE;
   } /* endif */

   usAction=accessSem((PHEADER)hsSprite,ACCSEM_SET);

   if (hsSprite->hpgPlay==NULL) {
      accessSem((PHEADER)hsSprite,usAction);
      return SPR_ERR_HASNOPLAYGROUND;
   } /* endif */

   *pptlPos=hsSprite->ptlPos;
   accessSem((PHEADER)hsSprite,usAction);
   return SPR_ERR_NOERROR;
}
Back

SprQuerySpriteRect
Since the bounding rectangle of a sprite varies with its position, and since a sprite cannot have a position until it is a member of a playground, this function checks for membership and fails if there is none. Note that the rectangle returned is all-inclusive, i.e. the upper and right edge of the rectangle is part of the sprite.


SPRERROR EXPENTRY SprQuerySpriteRect(HSPRITE hsSprite,PRECTL prclRect)
//-------------------------------------------------------------------------
// This function returns the bounding rectangle of the sprite at its
// current position.
//
// Input:  hsSprite - handle to the sprite
// Output:  prclRect - points to the current bounding rectangle
// Returns:  SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise
//-------------------------------------------------------------------------
{
   USHORT usAction;

   if (queryHandleType(hsSprite)!=QH_HSPRITE) {
      return SPR_ERR_BADHANDLE;
   } /* endif */

   usAction=accessSem((PHEADER)hsSprite,ACCSEM_SET);

   if (hsSprite->hpgPlay==NULL) {
      accessSem((PHEADER)hsSprite,usAction);
      return SPR_ERR_HASNOPLAYGROUND;
   } /* endif */

   prclRect->xLeft=hsSprite->ptlPos.x;
   prclRect->yBottom=hsSprite->ptlPos.y;
   prclRect->xRight=prclRect->xLeft+hsSprite->bmihBitmap.cx-1;
   prclRect->yTop=prclRect->yBottom+hsSprite->bmihBitmap.cy-1;
   accessSem((PHEADER)hsSprite,usAction);
   return SPR_ERR_NOERROR;
}
Back

SprQuerySpriteSize
Unlike SprQuerySpritePosition() and SprQuerySpriteRect(), SprQuerySpriteSize() is not variant, but is fixed at the time of creation, so we do not need to check for membership in a playground.


SPRERROR EXPENTRY SprQuerySpriteSize(HSPRITE hsSprite,PSIZEL pszlSize)
//-------------------------------------------------------------------------
// This function returns the current size of the sprite.
//
// Input:  hsSprite - handle to the sprite
// Output:  pszlSize - points to the current size
// Returns:  SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise
//-------------------------------------------------------------------------
{
   USHORT usAction;

   if (queryHandleType(hsSprite)!=QH_HSPRITE) {
      return SPR_ERR_BADHANDLE;
   } /* endif */

   usAction=accessSem((PHEADER)hsSprite,ACCSEM_SET);
   pszlSize->cx=hsSprite->bmihBitmap.cx;
   pszlSize->cy=hsSprite->bmihBitmap.cy;
   accessSem((PHEADER)hsSprite,usAction);
   return SPR_ERR_NOERROR;
}
Back

SprQuerySpriteVisibility
There is nothing special about this function that needs to be noted.


SPRERROR EXPENTRY SprQuerySpriteVisibility(HSPRITE hsSprite,PBOOL
pbVisible)

//-------------------------------------------------------------------------
// This function returns the visibility state of the sprite
//
// Input:  hsSprite - handle to the sprite
// Output:  pbVisible - points to the visibility state
// Returns:  SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise
//-------------------------------------------------------------------------
{
   USHORT usAction;

   if (queryHandleType(hsSprite)!=QH_HSPRITE) {
      return SPR_ERR_BADHANDLE;
   } /* endif */

   usAction=accessSem((PHEADER)hsSprite,ACCSEM_SET);

   if (hsSprite->hpgPlay==NULL) {
      accessSem((PHEADER)hsSprite,usAction);
      return SPR_ERR_HASNOPLAYGROUND;
   } /* endif */

   *pbVisible=hsSprite->bVisible;
   accessSem((PHEADER)hsSprite,usAction);
   return SPR_ERR_NOERROR;
}
Back

SprQueryUpdateFlag
There is nothing special about this function that needs to be noted.


SPRERROR EXPENTRY SprQueryUpdateFlag(HPLAYGROUND hpgPlay,PBOOL pbUpdate)
//-------------------------------------------------------------------------
// This function returns the setting of the update flag.  See the notes
// for SprSetUpdateFlag() for more information about this setting.
//
// Input:  hpgPlay - handle to the playground
//         pbUpdate - points to the variable to receive the update flag
// Returns:  SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise
//-------------------------------------------------------------------------
{
   USHORT usAction;

   if (queryHandleType(hpgPlay)!=QH_HPLAYGROUND) {
      return SPR_ERR_BADHANDLE;
   } /* endif */

   usAction=accessSem((PHEADER)hpgPlay,ACCSEM_SET);
   *pbUpdate=hpgPlay->bUpdate;
   accessSem((PHEADER)hpgPlay,usAction);
   return SPR_ERR_NOERROR;
}
Back

SprRemoveSprite
This function removes a sprite from the ahsSprites field of the HPLAYGROUND structure and moves all members after the sprite down one slot in the array.


SPRERROR EXPENTRY SprRemoveSprite(HPLAYGROUND hpgPlay,HSPRITE hsSprite)
//-------------------------------------------------------------------------
// This function removes the sprite from the membership list of the
// specified playground.  The sprite can then be added to another
// playground, or this one at a later time.
//
// Since there is a limited number of sprites that can be members of
// a playground, this function can be used to temporarily remove unused
// sprites from a playground so that others can be used.
//
// Input:  hpgPlay - handle to the playground
//         hsSprite - handle to the sprite to remove
// Returns:  SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise
//-------------------------------------------------------------------------
{
   USHORT usAction;
   ULONG ulIndex;

   if (queryHandleType(hpgPlay)!=QH_HPLAYGROUND) {
      return SPR_ERR_BADHANDLE;
   } else
   if (queryHandleType(hsSprite)!=QH_HSPRITE) {
      return SPR_ERR_BADHANDLE;
   } /* endif */

   usAction=accessSem((PHEADER)hpgPlay,ACCSEM_SET);

   for (ulIndex=0; ulIndex<hpgPlay->ulNumMembers; ulIndex++) {
      if (hpgPlay->ahsSprites[ulIndex]==hsSprite) {
         break;
      } /* endif */
   } /* endfor */

   if (ulIndex==hpgPlay->ulNumMembers) {
      accessSem((PHEADER)hpgPlay,usAction);
      return SPR_ERR_HASNOPLAYGROUND;
   } /* endif */

   //----------------------------------------------------------------------
   // Adjust the member array by moving all of the sprites after the one
   // being removed to the slot just before there current position.  Then,
   // decrement the number of members and we're done.
   //----------------------------------------------------------------------
   hpgPlay->ulNumMembers--;

   while (ulIndex<hpgPlay->ulNumMembers) {
      hpgPlay->ahsSprites[ulIndex]=hpgPlay->ahsSprites[ulIndex+1];
      ulIndex++;
   } /* endwhile */

   hpgPlay->ahsSprites[ulIndex]=NULL;
   hsSprite->hpgPlay=NULL;
   accessSem((PHEADER)hpgPlay,usAction);
   return SPR_ERR_NOERROR;
}
Back

SprSetPlaygroundBack
There is nothing special about this function that needs to be noted.


SPRERROR EXPENTRY SprSetPlaygroundBack(HPLAYGROUND hpgPlay,
                                       HBITMAP hbmNew,
                                       HBITMAP *phbmOld)
//-------------------------------------------------------------------------
// This function sets the background bitmap of the playground.
//
// Note that, once this function is called, the bitmap is managed by
// the sprite subsystem.  The bitmap should *NOT* be deleted by the
// application unless the bitmap is "unset" from the playground (by
// calling this function again with a different handle).
//
// Input:  hpgPlay - handle to the playground
//         hbmNew - handle to the new bitmap to used as the background
// Output:  phbmOld - points to the handle to the old background bitmap.
//          This can be NULL, meaning that the application isn't interested
//          in receiving this value.
// Returns:  SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise
//-------------------------------------------------------------------------
{
   USHORT usAction;

   if (queryHandleType(hpgPlay)!=QH_HPLAYGROUND) {
      return SPR_ERR_BADHANDLE;
   } /* endif */

   usAction=accessSem((PHEADER)hpgPlay,ACCSEM_SET);

   if (phbmOld!=NULL) {
      *phbmOld=hpgPlay->hbmBack;
   } /* endif */

   hpgPlay->hbmBack=hbmNew;

   //----------------------------------------------------------------------
   // We're only interested in the cx and cy fields
   //----------------------------------------------------------------------
   hpgPlay->bmihBack.cbFix=16;
   GpiQueryBitmapInfoHeader(hpgPlay->hbmBack,&hpgPlay->bmihBack);

   accessSem((PHEADER)hpgPlay,usAction);
   return SPR_ERR_NOERROR;
}
Back

SprSetPlaygroundColor
Note that we do not force a repaint, since I didn't want to deal with passing in an HPS on too many functions. It is conceivable that the background color can be set immediately after creation of the playground and let it get displayed the next time WM_PAINT is processed.


SPRERROR EXPENTRY SprSetPlaygroundColor(HPLAYGROUND hpgPlay,LONG
lBackColor)

//-------------------------------------------------------------------------
// This function sets the new background color of the playground and is
// only valid if the playground doesn't have a bitmap.
//
// Input:  hpgPlay - handle to the playground
//         lBackColor - specifies the new background color
// Returns:  SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise
//-------------------------------------------------------------------------
{
   USHORT usAction;

   if (queryHandleType(hpgPlay)!=QH_HPLAYGROUND) {
      return SPR_ERR_BADHANDLE;
   } /* endif */

   usAction=accessSem((PHEADER)hpgPlay,ACCSEM_SET);

   if (hpgPlay->hbmBack!=NULLHANDLE) {
      accessSem((PHEADER)hpgPlay,usAction);
      return SPR_ERR_HASBACKGROUND;
   } /* endif */

   hpgPlay->lBackColor=lBackColor;
   accessSem((PHEADER)hpgPlay,usAction);
   return SPR_ERR_NOERROR;
}
Back

SprSetPlaygroundSize
This function is allowed only if there is no background bitmap.


SPRERROR EXPENTRY SprSetPlaygroundSize(HPLAYGROUND hpgPlay,PSIZEL pszlSize)
//-------------------------------------------------------------------------
// This function sets the playground size for playgrounds that do not have
// a bitmap set as the background.
//
// Input:  hpgPlay - handle to the playground
//         pszlSize - points to the size of the playground
// Returns:  SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise
//-------------------------------------------------------------------------
{
   USHORT usAction;

   if (queryHandleType(hpgPlay)!=QH_HPLAYGROUND) {
      return SPR_ERR_BADHANDLE;
   } /* endif */

   usAction=accessSem((PHEADER)hpgPlay,ACCSEM_SET);

   if (hpgPlay->hbmBack!=NULLHANDLE) {
      accessSem((PHEADER)hpgPlay,usAction);
      return SPR_ERR_HASBACKGROUND;
   } /* endif */

   hpgPlay->bmihBack.cx=pszlSize->cx;
   hpgPlay->bmihBack.cy=pszlSize->cy;

   accessSem((PHEADER)hpgPlay,usAction);
   return SPR_ERR_NOERROR;
}
Back

SprSetSpritePosition
There is nothing special about this function that needs to be noted.


SPRERROR EXPENTRY SprSetSpritePosition(HPS hpsDraw,
                                       HSPRITE hsSprite,
                                       PPOINTL pptlNew)
//-------------------------------------------------------------------------
// This function changes the position of the sprite.  This function is
// optimized so that, if the rectangle bounding the sprite at the new
// position overlaps the old, only one "bit blit" to the specified HPS
// is done, eliminating flicker.
//
// Input:  hpsDraw - handle to the HPS to draw the sprite in once it is
//                   moved
//         hsSprite - handle to the sprite
// Returns:  SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise
//-------------------------------------------------------------------------
{
   USHORT usAction;
   SIZEL szlPlay;
   SIZEL szlWork;
   RECTL rclOld;
   RECTL rclNew;
   RECTL rclUnion;
   RECTL rclSrc;
   RECTL rclDest;
   POINTL ptlWork;
   POINTL aptlPoints[4];

   if (queryHandleType(hsSprite)!=QH_HSPRITE) {
      return SPR_ERR_BADHANDLE;
   } /* endif */

   usAction=accessSem((PHEADER)hsSprite,ACCSEM_SET);

   if (hsSprite->hpgPlay==NULL) {
      accessSem((PHEADER)hsSprite,usAction);
      return SPR_ERR_HASNOPLAYGROUND;
   } /* endif */

   if ((hsSprite->bVisible) && (hsSprite->hpgPlay->bUpdate)) {
      szlWork.cx=MAX_SPRITE_CX*2;
      szlWork.cy=MAX_SPRITE_CY*2;

      SprQueryPlaygroundSize(hsSprite->hpgPlay,&szlPlay);
Note how we query the sprite rectangle before and after the position changes and then call WinUnionRect() to check for overlap. This determines our course of action. If there is no overlap, call drawBackAt() and SprDrawSprite() to erase the sprite at its old position and redraw at its new position. Otherwise, perform the delta processing (see below).

      SprQuerySpriteRect(hsSprite,&rclOld);
      hsSprite->ptlPos=*pptlNew;
      SprQuerySpriteRect(hsSprite,&rclNew);

      WinUnionRect(hsSprite->habAnchor,&rclUnion,&rclOld,&rclNew);

      if ((rclUnion.xRight-rclUnion.xLeft>MAX_SPRITE_CX*2) ||
          (rclUnion.yTop-rclUnion.yBottom>MAX_SPRITE_CY*2)) {

         rclSrc.xLeft=rclOld.xLeft;
         rclSrc.yBottom=rclOld.yBottom;
         rclSrc.xRight=rclSrc.xLeft+hsSprite->bmihBitmap.cx;
         rclSrc.yTop=rclSrc.yBottom+hsSprite->bmihBitmap.cy;

         drawBackAt(hpsDraw,hsSprite->hpgPlay,NULL,NULL,&rclSrc);

         SprDrawSprite(hpsDraw,hsSprite);
      } else {
rclUnion contains the bounding rectangle of the old and new positions, so first transfer this rectangle from the background to the workarea (offset by (-rclUnion.xLeft,-rclUnion.yBottom)).

         rclSrc=rclUnion;
         rclSrc.xRight++;
         rclSrc.yTop++;

         rclDest.xLeft=0;
         rclDest.yBottom=0;
         rclDest.xRight=rclUnion.xRight-rclUnion.xLeft;
         rclDest.yTop=rclUnion.yTop-rclUnion.yBottom;

         drawBackAt(hsSprite->hpgPlay->hpsWork,
                    hsSprite->hpgPlay,
                    &rclDest,
                    &szlWork,
                    &rclSrc);
Once the background has been drawn, call drawSpriteAt() with a position also offset by (-rclUnion.xLeft,-rclUnion.yBottom). This completes our drawing in the workarea; now we simply need to remove the offset, clip to the playground, and call GpiBitBlt() to transfer the entire rclUnion-sized rectangle from the workarea to the screen.

         ptlWork.x=hsSprite->ptlPos.x-rclUnion.xLeft;
         ptlWork.y=hsSprite->ptlPos.y-rclUnion.yBottom;


drawSpriteAt(hsSprite->hpgPlay->hpsWork,hsSprite,&szlWork,&ptlWork);

         //----------------------------------------------------------------
         // GpiBitBlt is non-inclusive on source AND target
         //----------------------------------------------------------------
         aptlPoints[0].x=rclUnion.xLeft;
         aptlPoints[0].y=rclUnion.yBottom;
         aptlPoints[1].x=rclUnion.xRight+1;
         aptlPoints[1].y=rclUnion.yTop+1;
         aptlPoints[2].x=0;
         aptlPoints[2].y=0;
         aptlPoints[3].x=rclUnion.xRight-rclUnion.xLeft+1;
         aptlPoints[3].y=rclUnion.yTop-rclUnion.yBottom+1;

         if (clipBltPoints(hsSprite->habAnchor,aptlPoints,&szlPlay)) {
            GpiBitBlt(hpsDraw,
                      hsSprite->hpgPlay->hpsWork,
                      4,
                      aptlPoints,
                      ROP_SRCCOPY,
                      BBO_IGNORE);
         } /* endif */
      } /* endif */
   } else {
      hsSprite->ptlPos=*pptlNew;
   } /* endif */

   accessSem((PHEADER)hsSprite,usAction);
   return SPR_ERR_NOERROR;
}
Back

SprSetSpriteVisibility
There is nothing special about this function that needs to be noted.


SPRERROR EXPENTRY SprSetSpriteVisibility(HPS hpsDraw,
                                         HSPRITE hsSprite,
                                         BOOL bVisible)
//-------------------------------------------------------------------------
// This function shows or hides a sprite.
//
// Input:  hpsDraw - handle to the HPS to draw in once the sprite is
//                   shown or hidden
//         hsSprite - handle to the sprite
//         bVisible - new visibility state
// Returns:  SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise
//-------------------------------------------------------------------------
{
   USHORT usAction;
   RECTL rclSprite;

   if (queryHandleType(hsSprite)!=QH_HSPRITE) {
      return SPR_ERR_BADHANDLE;
   } /* endif */

   usAction=accessSem((PHEADER)hsSprite,ACCSEM_SET);

   if (hsSprite->hpgPlay==NULL) {
      accessSem((PHEADER)hsSprite,usAction);
      return SPR_ERR_HASNOPLAYGROUND;
   } /* endif */

   if (hsSprite->bVisible!=bVisible) {
      hsSprite->bVisible=bVisible;

      if (hsSprite->hpgPlay->bUpdate) {
         if (hsSprite->bVisible) {
            SprDrawSprite(hpsDraw,hsSprite);
         } else {
            rclSprite.xLeft=hsSprite->ptlPos.x;
            rclSprite.yBottom=hsSprite->ptlPos.y;
            rclSprite.xRight=rclSprite.xLeft+hsSprite->bmihMask.cx;
            rclSprite.yTop=rclSprite.yBottom+hsSprite->bmihMask.cy;

            drawBackAt(hpsDraw,hsSprite->hpgPlay,NULL,NULL,&rclSprite);
         } /* endif */
      } /* endif */
   } /* endif */

   accessSem((PHEADER)hsSprite,usAction);
   return SPR_ERR_NOERROR;
}
Back

SprSetUpdateFlag
There is nothing special about this function that needs to be noted.


SPRERROR EXPENTRY SprSetUpdateFlag(HPLAYGROUND hpgPlay,BOOL bUpdate)
//-------------------------------------------------------------------------
// This function sets the update flag for the playground.  If FALSE, no
// drawing actually takes place in any of the functions requiring an HPS,
// and the value of the HPS handle may be NULLHANDLE.  If TRUE, updating
// is reenabled, but you should still call SprDrawPlayground() to refresh
// the screen with the current contents.
//
// Input:  hpgPlay - handle to the playground
//         bUpdate - specifies the new update flag
// Returns:  SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise
//-------------------------------------------------------------------------
{
   USHORT usAction;

   if (queryHandleType(hpgPlay)!=QH_HPLAYGROUND) {
      return SPR_ERR_BADHANDLE;
   } /* endif */

   usAction=accessSem((PHEADER)hpgPlay,ACCSEM_SET);
   hpgPlay->bUpdate=bUpdate;
   accessSem((PHEADER)hpgPlay,usAction);
   return SPR_ERR_NOERROR;
}
Back