Newsgroups: comp.sys.mac.programmer
Path: utzoo!utgpu!news-server.csri.toronto.edu!mailrus!wuarchive!psuvax1!swatsun!jackiw
From: jackiw@cs.swarthmore.edu (Nick Jackiw)
Subject: Re: How do you grab lines like MacDraw?
Message-ID: <89RN1MB@cs.swarthmore.edu>
Reply-To: jackiw@cs.swarthmore.edu (Nick Jackiw)
Organization: Visual Geometry Project, Swarthmore College, PA
References: <4176@dogie.macc.wisc.edu>
Date: Thu, 9 Aug 90 16:26:14 GMT

yahnke@vms.macc.wisc.edu (Ross Yahnke, MACC) writes:
> In programming a MacDraw style environment where the
> user can click-n-drag objects around, it's ez enuf to grab
> objects with mass like squares and rectangles. You just see if
> the clicked on pixel is in any of the regions defined by the
> objects, i.e., you do a "PtInRgn()" call. 
> 
> But what if the object is a line? Can a region be defined to
> be a line, w/no thickness? I'm posting this for a friend who
> said he tried creating a region w/only a line in it and
> subsequent PtInRgn calls would fail... any ideas?
> 
> >>> yahnke@macc.wisc.edu <<<

This has been gone over a couple of times in the past, and there are
two basic approaches which seem wise.  Regions are definitely *not*
wise: a region, internally, can be thought of as a list of rectangles
which define the space enclosed.  (This isn't quite accurate, but it's
sufficient...) Thus, while regions can efficiently model horizontal
and vertical lines (one rectangle, the length of the line by its
pixel width in dimension), diagonal lines can give rise to regions
which consist of hundreds of single-pixel rectangles.  Horribly memory
inefficient, and not the sort of data structures you want to create on
the fly every time your user clicks the mouse.

The two basic approaches, bitmap inclusion and algebraic solution, 
differ in their appeal.  The former offers elegant consistency and
broad applicability; the latter is the fastest and most memory-efficient.

Before describing them, let me note a few terms which are relevant to
both solutions.  You presumably have an object list (or array, or
whatever), which may or may not be ordered, describing all the objects
in your window.  You have your hit, a Quickdraw point expressed in
the same coordinate system as your object list.  And you may want to
have some slop--a pixel tolerance for hits so that if I hit one or
two pixels off the line I can still select it.

Bitmap Inclusion

This is Larry Rosenstein's favored approach.  Calculate a rectangle about
your hit-point which is as wide and high as your desired slop: say 3 pixels
square.  Now allocate an offscreen bitmap with these dimensions. (Or better,
allocate it in your application's initialization stage, and simply substitute
this new rectangle for its bounds field when you get a hit.) Substitute this
bitmap for your window's (with SetPortBits), and erase it.  Now draw every
object in your object list with a solid black pattern.  This will be
quite speedy, in that Quickdraw will realize that most of them lie outside
of your portBits and not draw them at all.  After drawing each object,
check the actual bit-image you've allocated in the bitmap.  (For a 3x3
rectangle, this data need only be six bytes long.) If any bit has been
set--i. e. if Quickdraw has drawn your most recent object within a tiny
rectangle representing the user's hit--than that object is hit; select it.

The main advantage of this method is that it works for all types of objects.
If Quickdraw can draw it, bitmap inclusion will tell you if the user's hit
it.

Algebra

Alternately, you can calculate the distance from the point to the line,
and determine whether it is less than your maximum slop tolerance. The
formula for the distance between point (q,r) and a line passing through
points (x1,y1) and (x2,y2) is:

         |(q-y1)(x2-x1)-(p-x1)(y2-y1)| 
         -----------------------------
          sqrt[(x2-x1)^2+(y2-y1)^2]

which, if you precalculate the invariant portions and store them with the
object can reduce to two integer multiplications and a few subtractions
in your hit-check loop.  The disadvantage of this method is that you
have to implement separate arithmetic for each type of object; consequently,
there's more code to maintain and more potential bugs to crop up. However,
algorithms for all quickdraw objects (points, lines, circles, ovals, arcs,
and polygons) are available in any basic Computational Geometry text;
most of them are probably in an Algebra I text; and they will be
consistently faster and take up less run-time memory than the bitmap
inclusion method.  I prefer the algebraic solution, but then my program
tends to have windows with several hundred objects in it so speed is
more important than elegance to my situation.

Give a holler if you want source code for algebraic stuff; someone else
can probably provide a working example of bitmap inclusion.

-Nick



-- 
-----Nick Jackiw [jackiw@cs.swarthmore.edu|jackiw@swarthmr.bitnet------
"This orbe of starres fixed infinitely vp extendeth hitself in altitvde 
sphericallye, and therefore immovable the pallace of foelicity garnished
with perpetvall shining glorious lightes innvmerable." - Thomas Digges
