[comp.sys.mac.programmer] Tracking the mouse.

scott@scam.Berkeley.EDU (Scott Silvey) (10/31/90)

I have just written and debugged my very first Macintosh application!
  It is a simple program which allows you to draw a lot of squiggly
  lines wherever you drag the mouse.  

My problem is that when the user is drawing, the lines come out a bit too
  squiggly.  The mouse skips about erratically like it does when the wait
  cursor shows as the Mac is doing intensive disk I/O.  When the mouse 
  accelleration is high, this problem is the most pronounced, while it is
  reduced when I set the accelleration to 'graphics tablet' mode.  Since
  I may someday wish to make a serious application which tracks the mouse
  smoothly, I find this behaviour rather annoying.  MacPaint and other
  similar programs don't seem to have this trouble, so I can only assume
  I am going about the problem in a manner that is perhaps too brutish.

My speculation is that Mac is going off on some interrupt every few seconds
  or so, resulting in the cursor movement being interpolated over the period
  of time in which the interrupt is being serviced.  Now, I don't even
  know if the Mac even DOES such things ... I'm only guessing.

Does anyone out there perhaps have an idea of what the problem is?  Does
  anyone have code that work as it smoothly tracks the mouse?

I have a Mac SE, I'm using LSC 3.0, and I am NOT using MultiFinder.

Here is the code I use to do the drawing:


          do_line(theEvent)
              EventRecord *theEvent;
          {
              Point p;
          
              p = theEvent->where;
          
              GlobalToLocal(&p);
              MoveTo(p.h, p.v);
          
              do {
                  LineTo(p.h, p.v);
                  GetMouse(&p);
              } while (StillDown());
          
              save_image();
          }


I would appreciate any help you could give me.  

Thanks,

Scott

gurgle@well.sf.ca.us (Pete Gontier) (11/02/90)

In article <1990Oct31.100603.1989@agate.berkeley.edu> scott@xcf.berkeley.edu writes:
>I have just written and debugged my very first Macintosh application!
 
Alert the media! Another hacker conquers the learning cliff! :-)
 
>Does anyone out there perhaps have an idea of what the problem is?
 
Doesn't look like there is any *problem* with your routine. You've done it
all by the book, which is good.
 
>  Does anyone have code that work as it smoothly tracks the mouse?
 
Now the ugly answer. Sometimes you can't do everything by the book. :-)
The following suggestions apply only to short stretches of code and only
to situations in which you are fairly certain you won't be interrupted by
any other code that does anything similar to what you are doing. (In this case,
it sounds pretty safe.)
 
The Trap Dispatcher is a pig. It saves registers, calculates offsets, all
sorts of horrible things that take up time. Accordingly, you need to minimize
your trap calls.
   1) write your loops using the minimum amount of trap calls
   2) think about what those traps actually do -- you might be able to do
      the same thing -- convenience traps like SetPt come to mind
   3) cut the Trap Dispatcher out of the picture
      a) before entering your loop, find out the address of the code that
         will execute when you call your traps -- learn the use of CallPascal,
         which is a compiler trick, not a trap call
      b) make judgement calls about how to fake traps -- exercise what's
         called 'Toolbox Karma'
 
Now, here's the code you wrote that I would change:
 
>              do {
>                  LineTo(p.h, p.v);
>                  GetMouse(&p);
>              } while (StillDown());
 
The problem here, really, is that you are calling traps which call other traps.
StillDown calls Button, GetMouse calls GlobalToLocal. I might write something
like this (and I emphasize the _might_ -- I know this compiles, but that's it):
 
#define kLineToTrapWord           0xA891
#define kGlobalToLocalTrapWord    0xA871
 
        extern Boolean CrsrBusy           : 0x08CD;
        extern Point   Mouse              : 0x0830;
        extern Boolean MouseButtonState   : 0x0172;
 
        Point mousePt;
        long LineToTrapAddr, GlobalToLocalTrapAddr;
 
        LineToTrapAddr =
                NGetTrapAddress ( kLineToTrapWord, ToolTrap );
        GlobalToLocalTrapAddr =
                NGetTrapAddress ( kGlobalToLocalTrapWord, ToolTrap );
 
        if ( StillDown ( ) )
                while ( MouseButtonState ) {
                        while ( CrsrBusy ) ; /* just wait */
                        mousePt = Mouse;
                        CallPascal ( & mousePt, GlobalToLocalTrapAddr );
                        CallPascal ( Mouse.h, Mouse.v, LineToTrapAddr );
                }
 
Voila, no trap calls in the loop.
 
If you really want it to honk, do it in assembly, and all kinds of overhead
goes away. You can keep things lying around in registers and you don't
have to let the compiler do fourteen things when it only needs to do one.
 
Is this DTS-approved technique? Nope. Not a chance. Does it work? Yep.
(The next question is "Does it break anybody else?" Nope. At least not until
System 8.)
 
BTW, note that the "if ( StillDown ( ) ) while" strategy needs to be employed
whether you use my way or your way. You can't draw a line and then test for
StillDown at the bottom of the loop, because then you might as well just call
Button -- the user may already have made the mistake.


 Pete Gontier, gurgle@well.sf.ca.us
 Software Imagineer, Kiwi Software, Inc.

oster@well.sf.ca.us (David Phillip Oster) (11/02/90)

The overhead to call a few traps in a 
	while(StillDown()){
		MoveTo();
		LineTo();
	}
loop is totally lost in the time it takes to actually do the drawing.
The trap dispatcher overhead simply can't matter. You get responsiveness
in a situation like this by:
	draw only that part that changes.
	use XOR mode drawing, so a second draw can erase it,
	or, use an offscreen buffer that you CopyBits to the screen.
Even though the offscreen buffer is actually slower, its smoothness is
percieved by users as being faster. (To get flicker free, stamp your
offscreen bitmap with the image of the cursor.)
-- 
-- David Phillip Oster - Note new signature. Old one has gone Bye Bye.
-- oster@well.sf.ca.us = {backbone}!well!oster

smoke@well.sf.ca.us (Nicholas Jackiw) (11/03/90)

In article <21464@well.sf.ca.us> oster@well.sf.ca.us (David Phillip Oster) writes:
[blah blah blah]
>(To get flicker free, stamp your
>offscreen bitmap with the image of the cursor.)
>-- David Phillip Oster - Note new signature. Old one has gone Bye Bye.

What an excellent and obvious idea!  Makes me feel like a dolt, the
amount of satisfaction I get out of my offscreen routines despite the
fact that the cursor's quivering like a biker mad on booze and speed.

Here's some cheap code to accomplish it.  THINK Pascal 3.0. Note that
you'll not want to call it for the final frame of an animation, or
else the stamped cursor will linger permanently in that frame.

procedure StampCursor;

{By Nick Jackiw 11/2/90 - idea from David Phillip Oster.}
{Use wantonly and rewrite as necessary.  Color cursor}
{support should be a trivial addition.}

{StampCursor, assuming that thePort is currently set to an}
{offscreen bitmap which is going to be copied to the screen,}
{draws the current cursor at the current mouse location in that port.}
{This helps minimize the flicker that occurs when a cursorless}
{offscreen image is copied over the current screen cursor.}

{This implementation assumes the presence of a few global variables:}

{	- GlobalCurs is the cursor most recently set with SetCursor.}
{	- OffWorld and OnWorld are two bitmaps describing the offscreen}
{	  and onscreen drawing environments.}
{	- OffCWorld and OnCWorld are similar, but are pixmaps}
{	- hasCQD indicates whether to use pixmaps or bitmaps}

  var
   cursBits, maskBits: BitMap;
   aPt: point;

 begin
  GetMouse(aPt);{Let Quickdraw localize the mouseLoc to our offscreen port}
  with aPt do {then adjust it by the difference between offscreen and onscreen}
   if hasCQD then
    with OffCWorld^^.bounds do
     begin
      h := h + OnCWorld^^.bounds.left - left;
      v := v + OnCWorld^^.bounds.top - top
     end
   else
    with OffWorld.bounds do
     begin
      h := h + OnWorld.bounds.left - left;
      v := v + OnWorld.bounds.top - top
     end;

  with cursBits do {Generate bitmaps describing the cursor and its mask}
   begin	{in positions relative to the localized mouse point}
    with bounds do
     with GlobalCurs.HotSpot do
      with thePort^ do
       begin
       top := aPt.v - (v);
       left := aPt.h - (h);
       right := left + 16;
       bottom := top + 16
       end;
    rowBytes := 2;
    baseAddr := @GlobalCurs.data
   end;

  maskBits := cursBits;
  maskBits.baseAddr := @GlobalCurs.mask;

  with cursBits do {image and clip it appropriately to the present port}
   CopyMask(cursBits, maskBits, thePort^.portBits, bounds, bounds, bounds)
 end;

-- 
                              --- * ---
Nicholas Jackiw                Smoke@well.sf.ca.us | Jackiw@cs.swarthmore.edu
Key Curriculum Press, Inc.     Applelink: D3970    | (415) 548-2304
                              --- * ---