1317 lines
35 KiB
C
1317 lines
35 KiB
C
/*
|
|
* PlowSearch.c --
|
|
*
|
|
* Plowing.
|
|
* Shadow and other searches.
|
|
*
|
|
* *********************************************************************
|
|
* * Copyright (C) 1985, 1990 Regents of the University of California. *
|
|
* * Permission to use, copy, modify, and distribute this *
|
|
* * software and its documentation for any purpose and without *
|
|
* * fee is hereby granted, provided that the above copyright *
|
|
* * notice appear in all copies. The University of California *
|
|
* * makes no representations about the suitability of this *
|
|
* * software for any purpose. It is provided "as is" without *
|
|
* * express or implied warranty. Export of this software outside *
|
|
* * of the United States of America may require an export license. *
|
|
* *********************************************************************
|
|
*/
|
|
|
|
#ifndef lint
|
|
static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/plow/PlowSearch.c,v 1.1.1.1 2008/02/03 20:43:50 tim Exp $";
|
|
#endif /* not lint */
|
|
|
|
#include <stdio.h>
|
|
|
|
#include "utils/magic.h"
|
|
#include "utils/geometry.h"
|
|
#include "tiles/tile.h"
|
|
#include "utils/hash.h"
|
|
#include "database/database.h"
|
|
#include "plow/plowInt.h"
|
|
#include "utils/stack.h"
|
|
#include "textio/textio.h"
|
|
|
|
/* Argument used in shadow search */
|
|
struct shadow
|
|
{
|
|
Rect s_area; /* Area being searched */
|
|
TileTypeBitMask s_okTypes; /* Complement of this set is the RHS
|
|
* boundary of the shadow.
|
|
*/
|
|
Edge s_edge; /* Edge being built up */
|
|
int (*s_proc)(); /* Filter procedure */
|
|
ClientData s_cdata; /* Additional argument to (*s_proc)() */
|
|
};
|
|
|
|
/* Stack used by plowSrOutline */
|
|
Stack *plowOutlineStack = NULL;
|
|
|
|
#define FLUSHSTACK(s) while (STACKLOOK(s)) (void) STACKPOP(s)
|
|
|
|
/* Outline tracing: is tile inside the outline */
|
|
#define IsInside(tp, out) TTMaskHasType(&(out)->o_insideTypes, TiGetTypeExact(tp))
|
|
|
|
/* Forward declarations */
|
|
|
|
extern void plowSrOutlineInit();
|
|
extern void plowSrOutlineNext();
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* EXTENDOUTLINE --
|
|
*
|
|
* EXTENDOUTLINE(outline)
|
|
* Outline *outline;
|
|
* {
|
|
* }
|
|
*
|
|
* Used to extend outline->o_nextRect in the direction being followed,
|
|
* based on the tiles outline->o_nextIn and outline->o_nextOut.
|
|
*
|
|
* Assumes that outline->o_nextRect is initially a degenerate box equal
|
|
* to the starting point of the segment. We update exactly one coordinate
|
|
* of outline->o_nextRect, depending on the direction outline->o_nextDir:
|
|
*
|
|
* GEO_NORTH r_ytop
|
|
* GEO_SOUTH r_ybot
|
|
* GEO_EAST r_xtop
|
|
* GEO_WEST r_xbot
|
|
*
|
|
* Results:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* Updates outline->o_nextRect as described above.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
#define EXTENDOUTLINE(o) { \
|
|
Tile *in = (o)->o_nextIn; \
|
|
Tile *out = (o)->o_nextOut; \
|
|
switch ((o)->o_nextDir) \
|
|
{ \
|
|
case GEO_NORTH: (o)->o_nextRect.r_ytop=MIN(TOP(out),TOP(in)); break; \
|
|
case GEO_SOUTH: (o)->o_nextRect.r_ybot=MAX(BOTTOM(out),BOTTOM(in)); break; \
|
|
case GEO_EAST: (o)->o_nextRect.r_xtop=MIN(RIGHT(in),RIGHT(out)); break; \
|
|
case GEO_WEST: (o)->o_nextRect.r_xbot=MAX(LEFT(in),LEFT(out)); break; \
|
|
} \
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* STACKOUTLINE --
|
|
*
|
|
* STACKOUTLINE(outline)
|
|
* Outline *outline;
|
|
*
|
|
* Called whenever we advance to a new inside/outside tile to
|
|
* load the tile stack with the tiles on the opposite side of
|
|
* the tile we just advanced to.
|
|
*
|
|
* The tile stack contains all the tiles along the outside of
|
|
* outline->o_nextIn if we are going up or down, or all the
|
|
* tiles along the inside of outline->o_nextOut if we are going
|
|
* going left or right.
|
|
*
|
|
* Flushes the tile stack before we push any tiles of our own.
|
|
*
|
|
* Results:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* May push tiles on plowOutlineStack.
|
|
* Sets outline->o_nextIn if going east or west.
|
|
* Sets outline->o_nextOut if going north or south.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
#define STACKOUTLINE(o) { \
|
|
Tile *tp; \
|
|
\
|
|
FLUSHSTACK(plowOutlineStack); \
|
|
switch ((o)->o_nextDir) \
|
|
{ \
|
|
case GEO_NORTH: \
|
|
for (tp = TR((o)->o_nextIn); \
|
|
BOTTOM(tp) > (o)->o_nextRect.r_ybot; tp = LB(tp)) \
|
|
STACKPUSH((ClientData) tp, plowOutlineStack); \
|
|
(o)->o_nextOut = tp; \
|
|
break; \
|
|
case GEO_SOUTH: \
|
|
for (tp = BL((o)->o_nextIn); \
|
|
TOP(tp) < (o)->o_nextRect.r_ytop; tp = RT(tp)) \
|
|
STACKPUSH((ClientData) tp, plowOutlineStack); \
|
|
(o)->o_nextOut = tp; \
|
|
break; \
|
|
case GEO_EAST: \
|
|
for (tp = RT((o)->o_nextOut); \
|
|
LEFT(tp) > (o)->o_nextRect.r_xbot; tp = BL(tp)) \
|
|
STACKPUSH((ClientData) tp, plowOutlineStack); \
|
|
(o)->o_nextIn = tp; \
|
|
break; \
|
|
case GEO_WEST: \
|
|
for (tp = LB((o)->o_nextOut); \
|
|
RIGHT(tp) < (o)->o_nextRect.r_xtop; tp = TR(tp)) \
|
|
STACKPUSH((ClientData) tp, plowOutlineStack); \
|
|
(o)->o_nextIn = tp; \
|
|
break; \
|
|
} \
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* plowSrShadow --
|
|
*
|
|
* Shadow search.
|
|
* Searches area from left to right for edges whose far side is not in
|
|
* okTypes. The edges we find will have exactly one tile on each side.
|
|
*
|
|
* This is similar to an area search, except we call the procedure with
|
|
* edges instead of tiles:
|
|
*
|
|
* (*proc)(edge, cdata)
|
|
* Edge *edge;
|
|
* ClientData cdata;
|
|
* {
|
|
* }
|
|
*
|
|
* The Edge specifies the initial X location of the edge (e_x), the final
|
|
* X location (e_newx, taken from the TRAILING coordinate of the tile on the
|
|
* edge's right-hand side), and the edge's top and bottom (e_ytop, e_ybot).
|
|
* It also contains the plane on which this edge was found (our argument
|
|
* 'plane'), and the types of the tiles on each side of the edge (e_ltype
|
|
* and e_rtype).
|
|
*
|
|
* The client is allowed to modify e_newx, but none of the other fields
|
|
* of the Edge.
|
|
*
|
|
* If the procedure returns 1, we abort the shadow search.
|
|
*
|
|
* Results:
|
|
* Returns 1 if aborted, 0 if the search completed normally.
|
|
*
|
|
* Side effects:
|
|
* None.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
plowSrShadow(
|
|
int pNum, /* Plane from plowYankDef to search */
|
|
Rect *area, /* Area to search. Edges coincident with the
|
|
* right-hand side of this area are not seen;
|
|
* they must lie to the left of area->r_xtop.
|
|
*/
|
|
const TileTypeBitMask *okTypesp,
|
|
int (*proc)(), /* Function to apply at each edge */
|
|
ClientData cdata) /* Additional argument to pass to (*proc)() */
|
|
{
|
|
TileTypeBitMask okTypes = *okTypesp; /* TTMaskCopy(&okTypes, okTypesp) */
|
|
Plane *plane = plowYankDef->cd_planes[pNum];
|
|
struct shadow s;
|
|
Tile *tp;
|
|
int bottom, ret;
|
|
Point p;
|
|
|
|
ret = 0;
|
|
|
|
/* Copy our arguments into 's' */
|
|
s.s_area = *area;
|
|
s.s_okTypes = okTypes;
|
|
s.s_proc = proc;
|
|
s.s_cdata = cdata;
|
|
s.s_edge.e_use = (CellUse *) NULL;
|
|
s.s_edge.e_flags = 0;
|
|
s.s_edge.e_pNum = pNum;
|
|
s.s_edge.e_ytop = s.s_area.r_ytop;
|
|
|
|
/* Walk along the LHS of the sweep area from top to bottom */
|
|
p.p_x = s.s_area.r_xbot;
|
|
p.p_y = s.s_area.r_ytop - 1;
|
|
tp = plane->pl_hint;
|
|
while (p.p_y >= s.s_area.r_ybot)
|
|
{
|
|
/* Find the next tile along the LHS of the sweep area */
|
|
GOTOPOINT(tp, &p);
|
|
p.p_y = BOTTOM(tp) - 1;
|
|
|
|
bottom = MAX(BOTTOM(tp), s.s_area.r_ybot);
|
|
if (RIGHT(tp) >= s.s_area.r_xtop)
|
|
{
|
|
s.s_edge.e_ytop = bottom;
|
|
}
|
|
else if (plowShadowRHS(tp, &s, bottom))
|
|
{
|
|
ret = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
plane->pl_hint = tp;
|
|
return (ret);
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* plowShadowRHS --
|
|
*
|
|
* Walk along the right-hand side of tile 'tp', enumerating all edges formed
|
|
* with tiles whose RHS is not a member of s->s_okTypes, and whose tops are
|
|
* larger than 'bottomLeft'.
|
|
*
|
|
* Results:
|
|
* Returns 0 normally, but returns 1 if a client decided to
|
|
* interrupt the search by returning 1.
|
|
*
|
|
* Side effects:
|
|
* May call the filter function.
|
|
* Modifies s->s_edge.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
plowShadowRHS(tp, s, bottomLeft)
|
|
Tile *tp; /* Tile whose RHS is to be followed */
|
|
struct shadow *s; /* Shadow search argument */
|
|
int bottomLeft; /* Bottom of 'tp', clipped to area */
|
|
{
|
|
Tile *tpR;
|
|
int bottom, left;
|
|
|
|
/* Walk along the RHS of 'tp' from top to bottom */
|
|
tpR = TR(tp), left = LEFT(tpR);
|
|
do
|
|
{
|
|
/*
|
|
* Only process tiles between s->s_edge.e_ytop (on the top)
|
|
* and bottomLeft (on the bottom).
|
|
*/
|
|
bottom = MAX(bottomLeft, BOTTOM(tpR));
|
|
if (bottom < s->s_edge.e_ytop)
|
|
{
|
|
/* If tpR is not in okTypes, pass the edge to the client function */
|
|
if (!TTMaskHasType(&s->s_okTypes, TiGetTypeExact(tpR)))
|
|
{
|
|
/* Left, right hand types */
|
|
s->s_edge.e_ltype = TiGetTypeExact(tp);
|
|
s->s_edge.e_rtype = TiGetTypeExact(tpR);
|
|
|
|
/* Coordinates */
|
|
s->s_edge.e_x = left;
|
|
s->s_edge.e_newx = TRAILING(tpR);
|
|
s->s_edge.e_ybot = bottom;
|
|
|
|
if ((*s->s_proc)(&s->s_edge, s->s_cdata))
|
|
return (1);
|
|
|
|
/* Skip to bottom of the edge we just processed */
|
|
s->s_edge.e_ytop = s->s_edge.e_ybot;
|
|
}
|
|
else if (RIGHT(tpR) >= s->s_area.r_xtop)
|
|
{
|
|
/* Skip this edge segment */
|
|
s->s_edge.e_ytop = bottom;
|
|
}
|
|
else
|
|
{
|
|
if (plowShadowRHS(tpR, s, bottom))
|
|
return (1);
|
|
/* Here s->s_edge.e_ytop == bottom */
|
|
}
|
|
}
|
|
tpR = LB(tpR);
|
|
}
|
|
while (TOP(tpR) > bottomLeft);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* plowSrShadowInitial --
|
|
*
|
|
* Shadow search.
|
|
* Searches area from left to right for edges whose far side is not in
|
|
* okTypes, or whose near side is not in okTypes, and for which both
|
|
* sides are different types.
|
|
*
|
|
* See plowSrShadow() above for more details.
|
|
*
|
|
* Results:
|
|
* Returns 1 if aborted, 0 if the search completed normally.
|
|
*
|
|
* Side effects:
|
|
* None.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
plowSrShadowInitial(pNum, area, okTypes, proc, cdata)
|
|
int pNum; /* Plane from plowYankDef to search */
|
|
Rect *area; /* Area to search. Edges coincident with the
|
|
* right-hand side of this area are not seen;
|
|
* they must lie to the left of area->r_xtop.
|
|
*/
|
|
TileTypeBitMask okTypes;
|
|
int (*proc)(); /* Function to apply at each edge */
|
|
ClientData cdata; /* Additional argument to pass to (*proc)() */
|
|
{
|
|
Plane *plane = plowYankDef->cd_planes[pNum];
|
|
struct shadow s;
|
|
Tile *tp;
|
|
int bottom, ret;
|
|
Point p;
|
|
|
|
ret = 0;
|
|
|
|
/* Copy our arguments into 's' */
|
|
s.s_area = *area;
|
|
s.s_okTypes = okTypes;
|
|
s.s_proc = proc;
|
|
s.s_cdata = cdata;
|
|
s.s_edge.e_use = (CellUse *) NULL;
|
|
s.s_edge.e_flags = 0;
|
|
s.s_edge.e_pNum = pNum;
|
|
s.s_edge.e_ytop = s.s_area.r_ytop;
|
|
|
|
/* Walk along the LHS of the sweep area from top to bottom */
|
|
p.p_x = s.s_area.r_xbot;
|
|
p.p_y = s.s_area.r_ytop - 1;
|
|
tp = plane->pl_hint;
|
|
while (p.p_y >= s.s_area.r_ybot)
|
|
{
|
|
/* Find the next tile along the LHS of the sweep area */
|
|
GOTOPOINT(tp, &p);
|
|
p.p_y = BOTTOM(tp) - 1;
|
|
|
|
bottom = MAX(BOTTOM(tp), s.s_area.r_ybot);
|
|
if (RIGHT(tp) >= s.s_area.r_xtop)
|
|
{
|
|
s.s_edge.e_ytop = bottom;
|
|
}
|
|
else if (plowShadowInitialRHS(tp, &s, bottom))
|
|
{
|
|
ret = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
plane->pl_hint = tp;
|
|
return (ret);
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* plowShadowInitialRHS --
|
|
*
|
|
* Walk along the right-hand side of tile 'tp', enumerating all edges formed
|
|
* with tiles whose RHS is not a member of s->s_okTypes, or whose LHS is not
|
|
* a member of s->s_okTypes, and whose LHS and RHS are different, and whose
|
|
* tops are larger than 'bottomLeft'.
|
|
*
|
|
* Results:
|
|
* Returns 0 normally, but returns 1 if a client decided to
|
|
* interrupt the search by returning 1.
|
|
*
|
|
* Side effects:
|
|
* May call the filter function.
|
|
* Modifies s->s_edge.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
plowShadowInitialRHS(tp, s, bottomLeft)
|
|
Tile *tp; /* Tile whose RHS is to be followed */
|
|
struct shadow *s; /* Shadow search argument */
|
|
int bottomLeft; /* Bottom of 'tp', clipped to area */
|
|
{
|
|
Tile *tpR;
|
|
int bottom, left;
|
|
|
|
/* Walk along the RHS of 'tp' from top to bottom */
|
|
tpR = TR(tp), left = LEFT(tpR);
|
|
do
|
|
{
|
|
/*
|
|
* Only process tiles between s->s_edge.e_ytop (on the top)
|
|
* and bottomLeft (on the bottom).
|
|
*/
|
|
bottom = MAX(bottomLeft, BOTTOM(tpR));
|
|
if (bottom < s->s_edge.e_ytop)
|
|
{
|
|
/* If tpR is not in okTypes, pass the edge to the client function */
|
|
if (TiGetTypeExact(tp) != TiGetTypeExact(tpR)
|
|
&& (!TTMaskHasType(&s->s_okTypes, TiGetTypeExact(tpR))
|
|
|| !TTMaskHasType(&s->s_okTypes, TiGetTypeExact(tp))))
|
|
{
|
|
/* Left, right hand types */
|
|
s->s_edge.e_ltype = TiGetTypeExact(tp);
|
|
s->s_edge.e_rtype = TiGetTypeExact(tpR);
|
|
|
|
/* Coordinates */
|
|
s->s_edge.e_x = left;
|
|
s->s_edge.e_newx = TRAILING(tpR);
|
|
s->s_edge.e_ybot = bottom;
|
|
|
|
if ((*s->s_proc)(&s->s_edge, s->s_cdata))
|
|
return (1);
|
|
|
|
/* Skip to bottom of the edge we just processed */
|
|
s->s_edge.e_ytop = s->s_edge.e_ybot;
|
|
}
|
|
else if (RIGHT(tpR) >= s->s_area.r_xtop)
|
|
{
|
|
/* Skip this edge segment */
|
|
s->s_edge.e_ytop = bottom;
|
|
}
|
|
else
|
|
{
|
|
if (plowShadowRHS(tpR, s, bottom))
|
|
return (1);
|
|
/* Here s->s_edge.e_ytop == bottom */
|
|
}
|
|
}
|
|
tpR = LB(tpR);
|
|
}
|
|
while (TOP(tpR) > bottomLeft);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* plowSrShadowBack --
|
|
*
|
|
* Shadow search, backwards.
|
|
* Searches area from right to left for edges whose far side is not in
|
|
* okTypes. The edges we find will have exactly one tile on each side.
|
|
*
|
|
* See the comments in plowSrShadow() for details on the client procedure.
|
|
*
|
|
* Results:
|
|
* Returns 1 if aborted, 0 if the search completed normally.
|
|
*
|
|
* Side effects:
|
|
* None.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
plowSrShadowBack(
|
|
int pNum, /* Plane from plowYankDef to search */
|
|
Rect *area, /* Area to search. Edges coincident with the
|
|
* left-hand side of this area are not seen;
|
|
* they must lie to the right of area->r_xbot.
|
|
*/
|
|
const TileTypeBitMask *okTypesp,
|
|
int (*proc)(), /* Function to apply at each edge */
|
|
ClientData cdata) /* Additional argument to pass to (*proc)() */
|
|
{
|
|
TileTypeBitMask okTypes = *okTypesp; /* TTMaskCopy(&okTypes, okTypesp) */
|
|
Plane *plane = plowYankDef->cd_planes[pNum];
|
|
struct shadow s;
|
|
Tile *tp;
|
|
int top, ret;
|
|
Point p;
|
|
|
|
ret = 0;
|
|
|
|
/* Copy our arguments into 's' */
|
|
s.s_area = *area;
|
|
s.s_okTypes = okTypes;
|
|
s.s_proc = proc;
|
|
s.s_cdata = cdata;
|
|
s.s_edge.e_use = (CellUse *) NULL;
|
|
s.s_edge.e_flags = 0;
|
|
s.s_edge.e_pNum = pNum;
|
|
s.s_edge.e_ybot = s.s_area.r_ybot;
|
|
|
|
/* Walk along the RHS of the sweep area from bottom to top */
|
|
p.p_x = s.s_area.r_xtop - 1;
|
|
p.p_y = s.s_area.r_ybot;
|
|
tp = plane->pl_hint;
|
|
while (p.p_y < s.s_area.r_ytop)
|
|
{
|
|
/* Find the next tile along the RHS of the sweep area */
|
|
GOTOPOINT(tp, &p);
|
|
|
|
p.p_y = TOP(tp);
|
|
|
|
top = MIN(TOP(tp), s.s_area.r_ytop);
|
|
if (LEFT(tp) <= s.s_area.r_xbot)
|
|
{
|
|
s.s_edge.e_ybot = top;
|
|
}
|
|
else if (plowShadowLHS(tp, &s, top))
|
|
{
|
|
ret = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* if ret = 1 then tp may point to an invalid tile --- Tim 6/15/01 */
|
|
if (!ret) plane->pl_hint = tp;
|
|
return (ret);
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* plowShadowLHS --
|
|
*
|
|
* Walk along the left-hand side of tile 'tp', enumerating all edges formed
|
|
* with tiles whose LHS is not a member of s->s_okTypes, and whose bottoms are
|
|
* less than 'topRight'.
|
|
*
|
|
* Results:
|
|
* Returns 0 normally, but returns 1 if a client decided to
|
|
* interrupt the search by returning 1.
|
|
*
|
|
* Side effects:
|
|
* May call the filter function.
|
|
* Modifies s->s_edge.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
plowShadowLHS(tp, s, topRight)
|
|
Tile *tp; /* Tile whose LHS is to be followed */
|
|
struct shadow *s; /* Shadow search argument */
|
|
int topRight; /* Top of 'tp', clipped to area */
|
|
{
|
|
Tile *tpL;
|
|
int top, right;
|
|
|
|
/* Walk along the LHS of 'tp' from bottom to top */
|
|
tpL = BL(tp), right = RIGHT(tpL);
|
|
do
|
|
{
|
|
/*
|
|
* Only process tiles between s->s_edge.e_ybot (on the bottom)
|
|
* and topRight (on the top).
|
|
*/
|
|
top = MIN(topRight, TOP(tpL));
|
|
if (top > s->s_edge.e_ybot)
|
|
{
|
|
/* If tpL is not in okTypes, pass the edge to the client function */
|
|
if (!TTMaskHasType(&s->s_okTypes, TiGetTypeExact(tpL)))
|
|
{
|
|
/* Left, right hand types */
|
|
s->s_edge.e_ltype = TiGetTypeExact(tpL);
|
|
s->s_edge.e_rtype = TiGetTypeExact(tp);
|
|
|
|
/* Coordinates */
|
|
s->s_edge.e_x = right;
|
|
s->s_edge.e_newx = TRAILING(tp);
|
|
s->s_edge.e_ytop = top;
|
|
|
|
if ((*s->s_proc)(&s->s_edge, s->s_cdata))
|
|
return (1);
|
|
|
|
/* Skip to top of the edge we just processed */
|
|
s->s_edge.e_ybot = s->s_edge.e_ytop;
|
|
}
|
|
else if (LEFT(tpL) <= s->s_area.r_xbot)
|
|
{
|
|
/* Skip this edge segment */
|
|
s->s_edge.e_ybot = top;
|
|
}
|
|
else
|
|
{
|
|
if (plowShadowLHS(tpL, s, top))
|
|
return (1);
|
|
/* Here s->s_edge.e_ybot == top */
|
|
}
|
|
}
|
|
tpL = RT(tpL);
|
|
}
|
|
while (BOTTOM(tpL) < topRight);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* plowAtomize --
|
|
*
|
|
* Given a geometrical edge (the LHS of the rectangle 'rect'), call
|
|
* (*proc)() for each real Edge (between two tiles) found along
|
|
* the geometrical edge. Each Edge added will have a new x value of
|
|
* rect->r_xtop.
|
|
*
|
|
* The procedure (*proc)() should be the same kind (in arguments and
|
|
* return value) as that used in plowSrShadow() above.
|
|
*
|
|
* Don't call the procedure if the final coordinate of the tiles along
|
|
* a given Edge is already farther right than rect->r_xtop.
|
|
*
|
|
* Results:
|
|
* Returns 1 if (*proc)() returned 1 to abort the atomizing;
|
|
* otherwise, returns 0.
|
|
*
|
|
* Side effects:
|
|
* Whatever the client procedure has.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
plowAtomize(pNum, rect, proc, cdata)
|
|
int pNum; /* Plane from plowYankDef to search */
|
|
Rect *rect; /* LHS is the geometrical edge we search; each
|
|
* Edge found will have an e_newx coordinate
|
|
* equal to the RHS of this rect.
|
|
*/
|
|
int (*proc)(); /* Procedure to apply to each Edge */
|
|
ClientData cdata; /* Additional argument to (*proc)() */
|
|
{
|
|
Tile *tpL, *tpR;
|
|
Plane *plane = plowYankDef->cd_planes[pNum];
|
|
Point startPoint;
|
|
Edge edge;
|
|
int ytop;
|
|
|
|
edge.e_x = rect->r_xbot;
|
|
edge.e_newx = rect->r_xtop;
|
|
edge.e_use = (CellUse *) NULL;
|
|
edge.e_flags = 0;
|
|
edge.e_pNum = pNum;
|
|
|
|
ytop = rect->r_ytop;
|
|
startPoint.p_x = rect->r_xbot;
|
|
startPoint.p_y = rect->r_ytop - 1;
|
|
|
|
/* Walk down the RHS of the edge */
|
|
tpR = plane->pl_hint;
|
|
GOTOPOINT(tpR, &startPoint);
|
|
plane->pl_hint = tpR;
|
|
for ( ; TOP(tpR) > rect->r_ybot; tpR = LB(tpR))
|
|
{
|
|
/* Only process edges that haven't moved far enough */
|
|
if (TRAILING(tpR) < rect->r_xtop)
|
|
{
|
|
edge.e_rtype = TiGetTypeExact(tpR);
|
|
edge.e_ybot = MAX(BOTTOM(tpR), rect->r_ybot);
|
|
|
|
/* Walk up the LHS of the edge */
|
|
for (tpL = BL(tpR); BOTTOM(tpL) < ytop; tpL = RT(tpL))
|
|
{
|
|
/* Only process edges above the bottom of the clip area */
|
|
if (TOP(tpL) > edge.e_ybot)
|
|
{
|
|
edge.e_ytop = MIN(ytop, TOP(tpL));
|
|
edge.e_ltype = TiGetTypeExact(tpL);
|
|
if ((*proc)(&edge, cdata))
|
|
return (1);
|
|
edge.e_ybot = edge.e_ytop;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Advance the top of the clipping are downward */
|
|
ytop = BOTTOM(tpR);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* plowSrOutline --
|
|
*
|
|
* Outline enumeration.
|
|
* This procedure follows the outline of a collection of materials in a
|
|
* counter-clockwise direction. For each edge found while moving in a
|
|
* direction specified in dirMask, call a supplied client procedure:
|
|
*
|
|
* (*proc)(outline, cdata)
|
|
* Outline *outline;
|
|
* ClientData cdata;
|
|
* {
|
|
* }
|
|
*
|
|
* The bits in dirMask are GMASK_SOUTH, GMASK_NORTH, GMASK_EAST,
|
|
* or GMASK_WEST. They may be or'd together in any combination.
|
|
*
|
|
* The Outline gives a degenerate rectangle for this segment of the
|
|
* outline. Either this rectangle has zero height, or zero width.
|
|
* It also contains the plane on which this segment was found (our
|
|
* argument 'plane'), and pointers to the tiles on each side of the
|
|
* outline (o_inside, o_outside). Finally, it contains the direction
|
|
* followed before this segment (o_prevDir), the direction being
|
|
* followed along this segment (o_currentDir), and the direction to
|
|
* be followed after this segment (o_nextDir).
|
|
*
|
|
* If the procedure returns 1, we abort the outline enumeration.
|
|
* Otherwise, we keep going indefinitely.
|
|
*
|
|
* Results:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* Whatever occur as a result of applying the supplied client
|
|
* procedure.
|
|
*
|
|
* Assumptions:
|
|
* The interior of the region whose perimeter is being traced
|
|
* cannot extend to infinity in any direction. Hence it is unwise
|
|
* to try tracing the perimeter of a region consisting of empty
|
|
* space, unless the client can be counted on to abort the search
|
|
* by returning 1.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
void
|
|
plowSrOutline(
|
|
int pNum, /* Plane # in plowYankDef to search */
|
|
Point *startPoint, /* Point on boundary; material of types
|
|
* in insideTypes should be to the
|
|
* inside (as determined by initialDir
|
|
* below).
|
|
*/
|
|
const TileTypeBitMask *insideTypesp,/* Mask of types inside the region
|
|
* whose outline is being traced.
|
|
*/
|
|
int initialDir, /* Initial direction to go from the
|
|
* starting point. One of GEO_NORTH,
|
|
* or GEO_SOUTH.
|
|
*/
|
|
int dirMask, /* Mask of those directions for which
|
|
* we will call the client procedure.
|
|
*/
|
|
int (*proc)(), /* Client procedure */
|
|
ClientData cdata) /* Argument to client procedure */
|
|
{
|
|
TileTypeBitMask insideTypes = *insideTypesp; /* TTMaskCopy(&insideTypes, insideTypesp) */
|
|
Outline outline;
|
|
|
|
/*
|
|
* Initialize the stack used for processing tiles
|
|
* along the outside of the perimeter.
|
|
*/
|
|
if (plowOutlineStack == (Stack *) NULL)
|
|
plowOutlineStack = StackNew(50);
|
|
|
|
/*
|
|
* Push a bottom marker on the stack.
|
|
* This allows plowSrOutline to be re-entrant.
|
|
*/
|
|
STACKPUSH((ClientData) NULL, plowOutlineStack);
|
|
|
|
/*
|
|
* Start out going in the direction specified by initialDir.
|
|
* This direction is really just a hint; we may go either 90 degrees
|
|
* to the left or to the right if appropriate.
|
|
*/
|
|
outline.o_pNum = pNum;
|
|
outline.o_insideTypes = insideTypes;
|
|
outline.o_currentDir = initialDir;
|
|
outline.o_rect.r_ll = outline.o_rect.r_ur = *startPoint;
|
|
plowSrOutlineInit(&outline);
|
|
EXTENDOUTLINE(&outline);
|
|
|
|
/*
|
|
* Now we have found the initial segment and are ready for the main loop.
|
|
* On entry to each iteration of this loop, o_currentDir and o_nextDir
|
|
* are set from the previous iteration; we advance by one (current becomes
|
|
* prev, next becomes current) and find the next direction, etc. before
|
|
* calling the client procedure.
|
|
*/
|
|
for (;;)
|
|
{
|
|
/* Advance along the boundary */
|
|
outline.o_prevDir = outline.o_currentDir;
|
|
outline.o_currentDir = outline.o_nextDir;
|
|
outline.o_inside = outline.o_nextIn;
|
|
outline.o_outside = outline.o_nextOut;
|
|
outline.o_rect = outline.o_nextRect;
|
|
|
|
/* Find outline.o_nextDir and next two tiles along boundary */
|
|
plowSrOutlineNext(&outline);
|
|
EXTENDOUTLINE(&outline);
|
|
|
|
/* Now we know the next direction: apply the filter procedure */
|
|
if (GMaskHasDir(dirMask, outline.o_currentDir))
|
|
if ((*proc)(&outline, cdata))
|
|
break;
|
|
}
|
|
|
|
/* Flush anything remaining on the stack */
|
|
while (STACKPOP(plowOutlineStack) != (ClientData) NULL)
|
|
/* Nothing */;
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* plowSrOutlineInit --
|
|
*
|
|
* Initialization for outline enumeration.
|
|
* Find the first edge in outline enumeration.
|
|
* On entry, o_rect should be degenerate (ll == ur) around the starting
|
|
* point for the search. This starting point should be on the outline.
|
|
* The direction o_currentDir should be one of GEO_NORTH or GEO_SOUTH; we
|
|
* pretend as though we arrived at the starting point from this direction.
|
|
*
|
|
* Results:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* Fills in fields of 'outline':
|
|
* o_nextIn, o_nextOut -- tiles on inside and outside of
|
|
* current edge.
|
|
* o_nextDir -- direction of current edge; this will
|
|
* differ by at most 90 degrees from
|
|
* o_currentDir.
|
|
* o_nextRect -- current edge itself.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
void
|
|
plowSrOutlineInit(outline)
|
|
Outline *outline;
|
|
{
|
|
Plane *plane = plowYankDef->cd_planes[outline->o_pNum];
|
|
Tile *in, *out;
|
|
Point p;
|
|
|
|
outline->o_nextDir = outline->o_currentDir;
|
|
outline->o_nextRect = outline->o_rect;
|
|
switch (outline->o_nextDir)
|
|
{
|
|
case GEO_NORTH:
|
|
/*
|
|
* Find the tiles on either side of the starting point (+):
|
|
*
|
|
* |
|
|
* in | out
|
|
* --------+--------
|
|
* |
|
|
* |
|
|
*
|
|
* It is possible for 'in' and 'out' to be the same tile.
|
|
*/
|
|
out = plane->pl_hint;
|
|
p = outline->o_rect.r_ll;
|
|
GOTOPOINT(out, &p);
|
|
p.p_x--;
|
|
in = out;
|
|
GOTOPOINT(in, &p);
|
|
|
|
if (!IsInside(in, outline))
|
|
{
|
|
/*
|
|
* Tile 'in' is really outside the outline, so go left.
|
|
* We know that BOTTOM(in) == outline->o_rect.r_xbot because
|
|
* the starting point is on the outline. We want:
|
|
* |
|
|
* out |
|
|
* --------+--------
|
|
* in |
|
|
* |
|
|
*/
|
|
outline->o_nextDir = GEO_WEST;
|
|
outline->o_nextOut = in;
|
|
}
|
|
else if (IsInside(out, outline))
|
|
{
|
|
/*
|
|
* Tile 'out' is really inside the outline, so go right.
|
|
* We know that TOP(out) == outline->o_rect.r_xbot because
|
|
* the starting point is on the outline. We want:
|
|
* |
|
|
* | in
|
|
* --------+--------
|
|
* | out
|
|
* |
|
|
*/
|
|
outline->o_nextDir = GEO_EAST;
|
|
for (out = LB(out);
|
|
RIGHT(out) <= outline->o_rect.r_xbot; out = TR(out))
|
|
/* Nothing */;
|
|
outline->o_nextOut = out;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Tile 'in' is really inside the outline, and tile 'out'
|
|
* is really outside, so we already are where we want to be.
|
|
*/
|
|
outline->o_nextIn = in;
|
|
}
|
|
break;
|
|
case GEO_SOUTH:
|
|
/*
|
|
* Find the tiles on either side of the starting point (+):
|
|
*
|
|
* |
|
|
* |
|
|
* --------+--------
|
|
* out | in
|
|
* |
|
|
*
|
|
* It is possible for 'in' and 'out' to be the same tile.
|
|
*/
|
|
p.p_x = outline->o_rect.r_xbot - 1;
|
|
p.p_y = outline->o_rect.r_ybot - 1;
|
|
out = plane->pl_hint;
|
|
GOTOPOINT(out, &p);
|
|
p.p_x++;
|
|
in = out;
|
|
GOTOPOINT(in, &p);
|
|
|
|
if (!IsInside(in, outline))
|
|
{
|
|
/*
|
|
* Tile 'in' is really outside the outline, so go right.
|
|
* We know that TOP(in) == outline->o_rect.r_xbot because
|
|
* the starting point is on the outline. We want:
|
|
* |
|
|
* | in
|
|
* --------+--------
|
|
* | out
|
|
* |
|
|
*/
|
|
outline->o_nextDir = GEO_EAST;
|
|
outline->o_nextOut = in;
|
|
}
|
|
else if (IsInside(out, outline))
|
|
{
|
|
/*
|
|
* Tile 'out' is really inside the outline, so go left.
|
|
* We know that TOP(out) == outline->o_rect.r_xbot because
|
|
* the starting point is on the outline. We want:
|
|
* |
|
|
* out |
|
|
* --------+--------
|
|
* in |
|
|
* |
|
|
*/
|
|
outline->o_nextDir = GEO_WEST;
|
|
for (out = RT(out);
|
|
LEFT(out) >= outline->o_rect.r_xbot; out = BL(out))
|
|
/* Nothing */;
|
|
outline->o_nextOut = out;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Tile 'in' is really inside the outline, and tile 'out'
|
|
* is really outside, so we already are where we want to be.
|
|
*/
|
|
outline->o_nextIn = in;
|
|
}
|
|
break;
|
|
default:
|
|
TxError("Illegal initialDir (%d) for plowSrOutline\n",
|
|
outline->o_nextDir);
|
|
niceabort();
|
|
return;
|
|
}
|
|
|
|
STACKOUTLINE(outline);
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* plowSrOutlineNext --
|
|
*
|
|
* Find the next edge in outline enumeration.
|
|
*
|
|
* Results:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* Fills in fields of 'outline':
|
|
* o_nextIn, o_nextOut -- tiles on inside and outside of
|
|
* next edge.
|
|
* o_nextDir -- direction of next edge.
|
|
* o_nextRect -- next edge's rectangle
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
void
|
|
plowSrOutlineNext(outline)
|
|
Outline *outline;
|
|
{
|
|
Tile *tpL, *tpR;
|
|
|
|
/*
|
|
* Initialize the "next" segment to be the degenerate
|
|
* endpoint of the current segment.
|
|
*/
|
|
outline->o_nextDir = outline->o_currentDir;
|
|
switch (outline->o_currentDir)
|
|
{
|
|
case GEO_NORTH:
|
|
case GEO_EAST:
|
|
outline->o_nextRect.r_ur = outline->o_rect.r_ur;
|
|
outline->o_nextRect.r_ll = outline->o_rect.r_ur;
|
|
break;
|
|
case GEO_SOUTH:
|
|
case GEO_WEST:
|
|
outline->o_nextRect.r_ur = outline->o_rect.r_ll;
|
|
outline->o_nextRect.r_ll = outline->o_rect.r_ll;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If the tile stack was not yet empty, we do things the
|
|
* easy way. Advance to the next tile along the side of
|
|
* the one inside (if going north/south) or the one outside
|
|
* (if going east/west).
|
|
*/
|
|
if (STACKLOOK(plowOutlineStack))
|
|
{
|
|
switch (outline->o_currentDir)
|
|
{
|
|
/*
|
|
* Heading north.
|
|
*
|
|
* | nextOut
|
|
* nextIn +--------
|
|
* | outside
|
|
*
|
|
* Turn east if nextOut is in o_insideTypes.
|
|
* Otherwise, keep going north.
|
|
*/
|
|
case GEO_NORTH:
|
|
outline->o_nextOut = (Tile *) STACKPOP(plowOutlineStack);
|
|
if (IsInside(outline->o_nextOut, outline))
|
|
{
|
|
outline->o_nextOut = LB(outline->o_nextOut);
|
|
outline->o_nextDir = GEO_EAST;
|
|
}
|
|
break;
|
|
/*
|
|
* Heading south.
|
|
*
|
|
* outside |
|
|
* --------+ nextIn
|
|
* nextOut |
|
|
*
|
|
* Turn west if nextOut is in o_insideTypes.
|
|
* Otherwise, keep going south.
|
|
*/
|
|
case GEO_SOUTH:
|
|
outline->o_nextOut = (Tile *) STACKPOP(plowOutlineStack);
|
|
if (IsInside(outline->o_nextOut, outline))
|
|
{
|
|
outline->o_nextOut = RT(outline->o_nextOut);
|
|
outline->o_nextDir = GEO_WEST;
|
|
}
|
|
break;
|
|
/*
|
|
* Heading east.
|
|
*
|
|
* inside | nextIn
|
|
* --------+--------
|
|
* nextOut
|
|
*
|
|
* Turn north if nextIn is not in o_insideTypes.
|
|
* Otherwise, keep going east.
|
|
*/
|
|
case GEO_EAST:
|
|
outline->o_nextIn = (Tile *) STACKPOP(plowOutlineStack);
|
|
if (!IsInside(outline->o_nextIn, outline))
|
|
{
|
|
outline->o_nextIn = BL(outline->o_nextIn);
|
|
outline->o_nextDir = GEO_NORTH;
|
|
}
|
|
break;
|
|
/*
|
|
* Heading west.
|
|
*
|
|
* nextOut
|
|
* --------+--------
|
|
* nextIn | inside
|
|
*
|
|
* Turn south if nextIn is not in o_insideTypes.
|
|
* Otherwise, keep going west.
|
|
*/
|
|
case GEO_WEST:
|
|
outline->o_nextIn = (Tile *) STACKPOP(plowOutlineStack);
|
|
if (!IsInside(outline->o_nextIn, outline))
|
|
{
|
|
outline->o_nextIn = TR(outline->o_nextIn);
|
|
outline->o_nextDir = GEO_SOUTH;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Stack a new set of tiles along the opposite side if
|
|
* we changed direction, and set the appropriate one of
|
|
* o_nextIn (if going east/west) or o_nextOut (if going
|
|
* north/south).
|
|
*/
|
|
if (outline->o_nextDir != outline->o_currentDir)
|
|
STACKOUTLINE(outline);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* The tile stack was empty, so we must advance the primary
|
|
* tile (inside for north/south, outside for east/west).
|
|
* This code is more complex than that above, because we
|
|
* have three choices: continue going straight ahead, turn
|
|
* left, or turn right.
|
|
*/
|
|
switch (outline->o_currentDir)
|
|
{
|
|
/*
|
|
* Heading north.
|
|
*
|
|
* tpL tpR
|
|
* --------+
|
|
* nextIn |
|
|
*
|
|
* If tpL is outside, turn west.
|
|
* If tpR is inside, turn east.
|
|
* Otherwise, continue north.
|
|
*/
|
|
case GEO_NORTH:
|
|
tpL = RT(outline->o_nextIn);
|
|
if (!IsInside(tpL, outline))
|
|
{
|
|
outline->o_nextOut = tpL;
|
|
outline->o_nextDir = GEO_WEST;
|
|
break;
|
|
}
|
|
|
|
/* Find tpR */
|
|
if (RIGHT(tpL) > outline->o_nextRect.r_xbot) tpR = tpL;
|
|
else for (tpR = TR(tpL);
|
|
BOTTOM(tpR) > outline->o_nextRect.r_ybot; tpR = LB(tpR))
|
|
/* Nothing */;
|
|
if (IsInside(tpR, outline))
|
|
{
|
|
outline->o_nextOut = TR(outline->o_nextIn);
|
|
outline->o_nextDir = GEO_EAST;
|
|
}
|
|
else outline->o_nextIn = tpL;
|
|
break;
|
|
/*
|
|
* Heading south.
|
|
*
|
|
* | nextIn
|
|
* --------+--------
|
|
* tpL tpR
|
|
*
|
|
* If tpR is outside, turn east.
|
|
* If tpL is inside, turn west.
|
|
* Otherwise, continue south.
|
|
*/
|
|
case GEO_SOUTH:
|
|
tpR = LB(outline->o_nextIn);
|
|
if (!IsInside(tpR, outline))
|
|
{
|
|
outline->o_nextOut = tpR;
|
|
outline->o_nextDir = GEO_EAST;
|
|
break;
|
|
}
|
|
|
|
/* Find tpL */
|
|
if (LEFT(tpR) < outline->o_nextRect.r_xbot) tpL = tpR;
|
|
else for (tpL = BL(tpR);
|
|
TOP(tpL) < outline->o_nextRect.r_ytop; tpL = RT(tpL))
|
|
/* Nothing */;
|
|
if (IsInside(tpL, outline))
|
|
{
|
|
outline->o_nextOut = BL(outline->o_nextIn);
|
|
outline->o_nextDir = GEO_WEST;
|
|
}
|
|
else outline->o_nextIn = tpR;
|
|
break;
|
|
/*
|
|
* Heading east.
|
|
*
|
|
* tpL
|
|
* --------+
|
|
* nextOut | tpR
|
|
*
|
|
* If tpR is inside, turn south.
|
|
* If tpL is outside, turn north.
|
|
* Otherwise, continue east.
|
|
*/
|
|
case GEO_EAST:
|
|
tpR = TR(outline->o_nextOut);
|
|
if (TOP(tpR) > outline->o_nextRect.r_ybot) tpL = tpR;
|
|
else for (tpL = RT(tpR);
|
|
LEFT(tpL) > outline->o_nextRect.r_xbot; tpL = BL(tpL))
|
|
/* Nothing */;
|
|
|
|
if (!IsInside(tpL, outline))
|
|
{
|
|
outline->o_nextIn = RT(outline->o_nextOut);
|
|
outline->o_nextDir = GEO_NORTH;
|
|
}
|
|
else if (IsInside(tpR, outline))
|
|
{
|
|
outline->o_nextIn = tpR;
|
|
outline->o_nextDir = GEO_SOUTH;
|
|
}
|
|
else outline->o_nextOut = tpR;
|
|
break;
|
|
/*
|
|
* Heading west.
|
|
*
|
|
* tpL nextOut
|
|
* +--------
|
|
* tpR
|
|
*
|
|
* If tpL is inside, turn north.
|
|
* If tpR is outside, turn south.
|
|
* Otherwise, continue west.
|
|
*/
|
|
case GEO_WEST:
|
|
tpL = BL(outline->o_nextOut);
|
|
if (BOTTOM(tpL) < outline->o_nextRect.r_ybot) tpR = tpL;
|
|
else for (tpR = LB(tpL);
|
|
RIGHT(tpR) < outline->o_nextRect.r_xbot; tpR = TR(tpR))
|
|
/* Nothing */;
|
|
if (!IsInside(tpR, outline))
|
|
{
|
|
outline->o_nextIn = LB(outline->o_nextOut);
|
|
outline->o_nextDir = GEO_SOUTH;
|
|
}
|
|
else if (IsInside(tpL, outline))
|
|
{
|
|
outline->o_nextIn = tpL;
|
|
outline->o_nextDir = GEO_NORTH;
|
|
}
|
|
else outline->o_nextOut = tpL;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Stack a new set of tiles along the opposite side always,
|
|
* since we either advanced to the next tile or changed
|
|
* direction. Set the appropriate one of o_nextIn (if
|
|
* going east/west) or o_nextOut (if going north/south).
|
|
*/
|
|
STACKOUTLINE(outline);
|
|
}
|