~
BEGIN
maxnum: CornerStitching.Number = LAST[CornerStitching.Number]/2;
minnum: CornerStitching.Number = FIRST[CornerStitching.Number]/2;
empty: CornerStitching.Rect = [maxnum, maxnum, minnum, minnum];
Tesselation: TYPE ~ CornerStitching.Tesselation;
Rect: TYPE ~ CornerStitching.Rect;
Position: TYPE ~ CornerStitching.Position;
Tile: TYPE ~ CornerStitching.Tile;
Coord: TYPE ~ CornerStitching.Coord;
NEdge: PROCEDURE [t: REF Tile] RETURNS [INT] ~ INLINE {RETURN [t.en.pos.y]};
EEdge: PROCEDURE [t: REF Tile] RETURNS [INT] ~ INLINE {RETURN [PtoR[t.ne].pos.x]};
SEdge: PROCEDURE [t: REF Tile] RETURNS [INT] ~ INLINE {RETURN [t.pos.y]};
WEdge: PROCEDURE [t: REF Tile] RETURNS [INT] ~ INLINE {RETURN [t.pos.x]};
CoerceToTesselation:
PUBLIC
PROCEDURE [ref:
REF
ANY]
RETURNS [
REF Tesselation] ~ {
RETURN [NARROW[ref]] -- may raise NARROWREFFAULT
};
RangeValue: TYPE ~ {lesser, inside, greater};
-- Error Codes
TileValue: PUBLIC ERROR ~ CODE;
TileDeleted: PUBLIC ERROR ~ CODE;
TilePosition: ERROR ~ CODE;
AreaNotEmpty: ERROR ~ CODE;
TilesNotAdjacent: ERROR ~ CODE;
DegenerateTile: ERROR ~ CODE;
-- Co-ordinates at "infinity"
neLimitCoord: Coord ~ [x~INT.LAST, y~INT.LAST];
nwLimitCoord: Coord ~ [x~INT.FIRST, y~INT.LAST];
swLimitCoord: Coord ~ [x~INT.FIRST, y~INT.FIRST];
seLimitCoord: Coord ~ [x~INT.LAST, y~INT.FIRST];
-- House-keeping tile values to flag deleted tiles and tiles at "infinity"
guard: REF ANY ~ $CornerStitchingPrivateEdgeGuard;
deleted: REF ANY ~ $CornerStitchingPrivateDeletedTile;
-- Border Tiles (shared by all tesselations)
northLimit: REF Tile;
southLimit: REF Tile;
eastLimit: REF Tile;
westLimit: REF Tile;
InitTesselationBorderTiles:
PROCEDURE ~ {
northLimit ← NEW[Tile ← [pos~nwLimitCoord, value~guard]];
southLimit ← NEW[Tile ← [pos~swLimitCoord, value~guard]];
eastLimit ← NEW[Tile ← [pos~seLimitCoord, value~guard]];
westLimit ← NEW[Tile ← [pos~swLimitCoord, value~guard]];
-- The stitching is not quite Kosher at Guard corners.
-- Think of the guard tile as having bevelled ends. They fit together like a picture-frame and are stitched accordingly.
-- North-East
northLimit.ne ← RtoP[eastLimit];
eastLimit.en ← northLimit;
-- South-East
southLimit.en ← eastLimit;
southLimit.ne ← RtoP[eastLimit];
eastLimit.ws ← RtoP[southLimit];
eastLimit.sw ← southLimit;
-- North-West
westLimit.ne ← RtoP[northLimit];
westLimit.en ← northLimit;
northLimit.sw ← westLimit;
northLimit.ws ← RtoP[westLimit];
-- South-West
southLimit.sw ← westLimit;
westLimit.ws ← RtoP[southLimit];
};
NewTesselation:
PUBLIC
PROCEDURE
RETURNS [
REF Tesselation] ~ {
centreSpace: REF Tile ← NEW[Tile ← [en~northLimit, ne~RtoP[eastLimit], sw~westLimit, ws~RtoP[southLimit], pos~swLimitCoord]];
RETURN [NEW[Tesselation ← [southEast~centreSpace, current~centreSpace]]]
};
FreeTesselation:
PUBLIC
PROCEDURE [plane:
REF Tesselation] ~ {
-- All the following was made obsolete by making ne and ws LONG POINTERs
-- Corner stitched structures contain circular pointer chains. The reference count based garbage collector in Cedar cannot handle such structures, so this routine breaks cycles so that tiles can be freed. But we must be careful not to recurse too deeply or cedar will run out of MDS and go to fon-fon. Therefore we don't take the obvious fully recursive approach rather we apply EnumerateArea.
Smash: CornerStitching.PerTileProc -- [tile: TilePtr, data: REF ANY] RETURNS [REF ANY] -- ~ {
tile.ne ← tile.en ← NIL;
RETURN [ NIL]
};
-- Unfortunately the world here is not quite ansiotropic, so that it is fine to talk about points along the western and southern edges of the world, but very dangerous on the northern and eastern borders. The reason is simple, every point must be assigned to some tile. when points lie on tile boundary some arbitrary decision must be made, we choose to put such points in the northern or eastern tile. Thus points on the Northern and Eastern borders get put in the guard tiles, with often dire results. A quick and dirty fix takes the PRED of NE limits.
[] ← EnumerateArea[ plane, [ swLimitCoord.x, swLimitCoord.y, neLimitCoord.x.PRED, neLimitCoord.y.PRED], Smash, NIL, guard];
-- Acutally we only clear the design, this way we will still know what to do if subsequently passed such a Tesselation. Reassignment of the Tesselation through NewTesselation will free centreSpace.
centreSpace: REF Tile ← NEW[Tile ← [en~northLimit, ne~RtoP[eastLimit], sw~westLimit, ws~RtoP[southLimit], pos~swLimitCoord]];
plane.current ← plane.southEast ← centreSpace;
plane.deletionCache ← NIL; -- allow the cache to be garbage collected.
plane.tilesInTesselationCount ← 1;
};
ChangeRect:
PUBLIC
PROCEDURE [plane:
REF Tesselation, rect: Rect, newvalue:
REF
ANY ←
NIL, checkOldvalue:
BOOLEAN ←
TRUE, oldvalue:
REF
ANY ←
NIL] ~ {
SplitEWRunningBorder:
PROCEDURE [StartTile:
REF Tile, splitLine, lineEndpoint:
INT] ~
INLINE {
boundaryRider: REF Tile ← StartTile;
WHILE WEdge[boundaryRider] < lineEndpoint
DO
WHILE SEdge[boundaryRider] > splitLine DO boundaryRider ← PtoR[boundaryRider.ws] ENDLOOP;
-- The second clause here is necessary to avoid splitting tiles which will never be repaired by changeTile, and will thus not be made maximal north-south. Its kind of subtle, and probably more than a little ugly, but I can probably prove its correct if pressed.
IF SEdge[boundaryRider] < splitLine
AND (boundaryRider.value # newvalue
OR EEdge[boundaryRider] < lineEndpoint)
THEN
-- This tile straddles the border, chop chop.
boundaryRider ← NSSplit[ plane, boundaryRider, splitLine].larger;
boundaryRider ← PtoR[boundaryRider.ne]
ENDLOOP;
};
tile: REF Tile ← FindTileContainingPoint[plane.current, [x~rect.x1, y~rect.y2]];
-- tile contains nw corner (sort of!)
plane.current ← tile;
--split tiles on the north border.
SplitEWRunningBorder[ tile, rect.y2, rect.x2];
--split tiles on the south border.
SplitEWRunningBorder[ FindTileContainingPoint[plane.current, [x~rect.x1, y~rect.y1]],
rect.y1, rect.x2];
-- Now we start gobbling up all the tiles into wide flat bands of newvalue.
tile ← FindTileContainingPoint[plane.current, [x~rect.x1, y~rect.y2.PRED]];
-- First get our western border tile into shape.
DO
IF tile.value # newvalue
AND WEdge[tile] < rect.x1
THEN {
outsideTile: REF Tile;
[smaller~outsideTile, larger~tile] ← EWSplit[plane, tile, rect.x1];
-- Better make sure we keep outside tile in order NS-wise.
IF outsideTile.value = outsideTile.en.value
AND WEdge[outsideTile] = WEdge[outsideTile.en]
AND EEdge[outsideTile] = EEdge[outsideTile.en]
THEN
outsideTile ← NSMerge[plane, outsideTile, outsideTile.en];
IF outsideTile.value =
PtoR[outsideTile.ws].value
AND WEdge[outsideTile] = WEdge[
PtoR[outsideTile.ws]]
AND EEdge[outsideTile] = EEdge[
PtoR[outsideTile.ws]]
THEN
outsideTile ← NSMerge[plane, PtoR[outsideTile.ws], outsideTile]
};
DO
IF checkOldvalue AND tile.value # oldvalue THEN ERROR TileValue;
IF tile.value # newvalue
THEN {
IF EEdge[tile] > rect.x2
THEN {
outsideTile: REF Tile;
[smaller~tile, larger~outsideTile] ← EWSplit[plane, tile, rect.x2];
IF outsideTile.value = outsideTile.en.value
AND WEdge[outsideTile] = WEdge[outsideTile.en]
AND EEdge[outsideTile] = EEdge[outsideTile.en]
THEN
outsideTile ← NSMerge[plane, outsideTile, outsideTile.en];
IF outsideTile.value =
PtoR[outsideTile.ws].value
AND WEdge[outsideTile] = WEdge[
PtoR[outsideTile.ws]]
AND EEdge[outsideTile] = EEdge[
PtoR[outsideTile.ws]]
THEN
outsideTile ← NSMerge[plane, PtoR[outsideTile.ws], outsideTile]
};
tile ← ChangeTile[plane, tile, newvalue]
};
IF EEdge[tile] >= rect.x2 THEN EXIT;
tile ← PtoR[tile.ne];
WHILE SEdge[tile] >= rect.y2 DO tile ← PtoR[tile.ws] ENDLOOP; -- EMM
ENDLOOP;
IF WEdge[tile] > rect.x1 THEN ERROR;
IF SEdge[tile] <= rect.y1 THEN EXIT;
tile ← PtoR[tile.ws];
WHILE EEdge[tile] <= rect.x1 DO tile ← PtoR[tile.ne] ENDLOOP
ENDLOOP
};
ChangeTile:
PROCEDURE [plane:
REF Tesselation, tile:
REF Tile, newvalue:
REF
ANY ←
NIL]
RETURNS [
REF Tile] ~ {
-- Returns the northernmost tile in the changed region. (There is only one such tile by the maximal-EW-property)
swCorner: Coord ~ tile.pos;
neCorner: Coord ~ [x~EEdge[tile], y~NEdge[tile]];
tWest, tEast, tCentre: REF Tile;
tile.value ← newvalue; -- Make tile a newvalue tile, then restore maximal East-West strip property
-- First we tidy up any newvalue tiles that abut any of the tile's four corners, but extend beyond the corner in a North-South direction
tEast ← PtoR[tile.ne];
IF tEast.value = newvalue AND NEdge[tEast] > neCorner.y
THEN [] ← NSSplit[plane, tEast, neCorner.y];
tWest ← tile.en;
WHILE WEdge[tWest] >= swCorner.x DO tWest ← tWest.sw ENDLOOP;
-- Now we are at the tile whose east EEdge is >= swCorner.x but whose WEdge is < swCorner.x. In fact SEdge[tWest] < neCorner.y will only hold if EEdge = swCorner.x
IF tWest.value = newvalue AND SEdge[tWest] < neCorner.y THEN [] ← NSSplit[plane, tWest, neCorner.y];
tWest ← tile.sw;
IF tWest.value = newvalue AND SEdge[tWest] < swCorner.y
THEN [] ← NSSplit[plane, tWest, swCorner.y];
tEast ← PtoR[tile.ws];
WHILE EEdge[tEast] <= neCorner.x DO tEast ← PtoR[tEast.ne] ENDLOOP;
-- Analogous to split of NW corner.
IF tEast.value = newvalue AND NEdge[tEast] > swCorner.y
THEN [] ← NSSplit[plane, tEast, swCorner.y];
-- Now we convert the West and East adjacent tiles to maximal East-West strips
-- First run South to North along the West edge splitting North-South, and merging East-West the newvalue tile created from the tile being changed.
tWest ← tile.sw;
WHILE NEdge[tWest] < neCorner.y
DO
IF tWest.value # newvalue
AND tWest.en.value # newvalue
THEN
tWest ← tWest.en
ELSE {
tile ← NSSplit[plane, tile, NEdge[tWest]].larger;
IF tWest.value = newvalue
THEN [] ← EWMerge[plane, tWest, PtoR[tWest.ne]];
tWest ← tile.sw;
}
ENDLOOP;
-- tile is the northernmost strip in the changed area.
IF tWest.value = newvalue THEN tile ← EWMerge[plane, tWest, tile];
-- Now any maximal-EW-property violations of tile are confined to its Eastern border. There may however still be some pending merges at the northern and southern borders.
-- Run North to South along the East edge splitting North-South any newvalue tile to the East which abuts more than one newvalue tile in the changed area.
tEast ← PtoR[tile.ne];
WHILE SEdge[tEast] > swCorner.y
DO
tile ← tEast.sw;
IF (tEast.value = newvalue
OR
PtoR[tEast.ws].value = newvalue)
AND SEdge[tile] < SEdge[tEast]
THEN
[] ← NSSplit[plane, tile, SEdge[tEast]];
tEast ← PtoR[tEast.ws]
ENDLOOP;
-- Then run South to North along the East edge splitting North-South, and merging East-West the newvalue tiles created from the tile being changed.
tCentre ← tEast.sw;
WHILE NEdge[tCentre] <= swCorner.y
DO
tCentre ← tCentre.en;
ENDLOOP;
tile ← tCentre;
WHILE NEdge[tCentre] <= neCorner.y
DO
tile ← tCentre;
IF
PtoR[tCentre.ne].value # newvalue
THEN {
tCentre ← tCentre.en;
}
ELSE {
tCentre ← tCentre.en;
IF tile.ne = tCentre.ne
THEN
-- Should not occur at north.
[] ← NSSplit[plane, PtoR[tile.ne], NEdge[tile]];
tile ← EWMerge[plane, tile, PtoR[tile.ne]];
};
IF tile.value =
PtoR[tile.ws].value
AND WEdge[tile] = WEdge[
PtoR[tile.ws]]
AND EEdge[tile] = EEdge[
PtoR[tile.ws]]
THEN
tile ← NSMerge[plane, PtoR[tile.ws], tile]
ENDLOOP;
-- tile is the northernmost strip in the changed area.
-- It may need to be merged with the tile above.
IF tile.value = tCentre.value
AND WEdge[tile] = WEdge[tile.en]
AND EEdge[tile] = EEdge[tile.en]
THEN tile ← NSMerge[plane, tile, tile.en];
-- tCentre is still the northernmost strip in the changed area though it may extend north of it.
RETURN [tile]
};
AreaEmpty:
PUBLIC
PROCEDURE [plane:
REF Tesselation, rect: Rect, backgroundValue:
REF
ANY ←
NIL]
RETURNS [
BOOLEAN] ~ {
-- Return TRUE if all tiles in area are of value backgroundValue, else FALSE. Relies on the Maximal East-West strip property.
plane.current ← FindTileContainingPoint[plane.current, [x~rect.x1, y~rect.y1]];
FOR tile:
REF Tile ← plane.current, TileAbove[tile, rect.x1]
WHILE SEdge[tile] < rect.y2
DO
IF tile.value # backgroundValue OR EEdge[tile] < rect.x2 THEN RETURN [FALSE]
ENDLOOP;
RETURN [TRUE]
};
ContentsBound:
PUBLIC
PROCEDURE [plane:
REF Tesselation, rect: Rect, backgroundValue:
REF
ANY ←
NIL]
RETURNS [bBox: Rect] ~ {
-- ContentsBound returns a minimal bounding box for non-backgroundValue tiles in rect. Relies on the Maximal East-West strip property.
tile: REF Tile;
bBox ← empty;
IF rect.x1 = rect.x2 THEN ERROR DegenerateTile;
plane.current ← FindTileContainingPoint[plane.current, [x~rect.x1, y~rect.y1]];
FOR tile ← plane.current, TileAbove[tile, rect.x1]
DO
IF SEdge[tile] >= rect.y2 THEN RETURN; -- Tile is empty
IF tile.value # backgroundValue OR EEdge[tile] < rect.x2 THEN EXIT
ENDLOOP;
bBox.y1 ← MAX[ SEdge[tile], rect.y1];
WHILE SEdge[tile] < rect.y2
DO
IF tile.value # backgroundValue THEN { bBox.x1 ← rect.x1; EXIT}
-- else the east edge of tile must abut a non-backgroundValue tile (by the Maximal East-West strip property)
ELSE IF EEdge[tile] < rect.x2 AND EEdge[tile] < bBox.x1 THEN bBox.x1 ← EEdge[tile];
tile ← TileAbove[tile, rect.x1];
ENDLOOP;
tile ← FindTileContainingPoint[plane.current, [x~rect.x2.PRED, y~rect.y1]];
WHILE SEdge[tile] < rect.y2
DO
-- Note FindTileContainingPoint is assymetric so code is different here! (This is the cause of the ugly .PREDs)
IF tile.value # backgroundValue
THEN {
bBox.x2 ← rect.x2;
bBox.y2 ← NEdge[tile]
}
ELSE
IF WEdge[tile] > rect.x1
THEN {
IF WEdge[tile] > bBox.x2 THEN bBox.x2 ← WEdge[tile];
bBox.y2 ← NEdge[tile]
};
tile ← TileAbove[tile, rect.x2.PRED];
ENDLOOP;
};
EnumerateArea:
PUBLIC
PROCEDURE [plane:
REF Tesselation, rect: Rect, perTile: CornerStitching.PerTileProc ←
NIL, data:
REF
ANY ←
NIL, backgroundValue:
REF
ANY ←
NIL]
RETURNS [
REF
ANY] ~ {
-- Uses the tiles own links to maintain an implicit stack of tiles. Enumeration proceed in a manner which ensures that a tiles ne and en pointers will never be needed once that tile has appeared in the enumeration; this fact is exploited in FreeTesselation.
Defn: b is a's child iff b.sw = a.
ChildOf:
PROCEDURE [ me:
REF Tile, you:
REF Tile]
RETURNS [
BOOLEAN] ~
INLINE {
RETURN [ you.sw = me];
};
BrotherOf:
PROCEDURE [ me:
REF Tile, you:
REF Tile]
RETURNS [
BOOLEAN] ~
INLINE {
RETURN [ me.sw = you.sw];
};
EnumerateShadow:
PROCEDURE [tile:
REF Tile]
RETURNS [
REF Tile]~ {
seeking: {youth, experience} ← youth;
DO
IF seeking = youth
THEN {
child: REF Tile ← PtoR[tile.ne];
WHILE SEdge[child] >= rect.y2
DO
-- He ain't no son o' mine
child ← PtoR[child.ws]
ENDLOOP;
IF ChildOf[ me~tile, you~child]
AND WEdge[child] < rect.x2
AND SEdge[child] > rect.y1
THEN {
tile ← child;
LOOP
}
ELSE
seeking ← experience
};
-- Add tile to enumeration.
IF tile.value # backgroundValue
THEN
IF perTile # NIL THEN data ← perTile[ tile, data]
ELSE tileEnumeration ← CONS[ NEW[ CornerStitching.Region ← [
[ MAX[ WEdge[tile], rect.x1], MAX[ SEdge[tile], rect.y1], MIN[ EEdge[tile], rect.x2], MIN[ NEdge[tile], rect.y2]],
tile.value] ], tileEnumeration];
-- Hand control to older sibling or parent (if you're the oldest).
IF WEdge[tile] <= rect.x1
OR SEdge[tile] <= rect.y1
THEN
-- This tile is the result of spontaneous generation!
RETURN [tile];
IF BrotherOf[ me~tile, you~
PtoR[tile.ws]]
AND SEdge[
PtoR[tile.ws]] > rect.y1
THEN {
tile ← PtoR[tile.ws];
seeking ← youth
}
ENDLOOP
};
tileEnumeration: LIST OF REF CornerStitching.Region ← NIL;
borderTile: REF Tile ← FindTileContainingPoint[plane.current, [x~rect.x1, y~rect.y2]];
IF SEdge[borderTile] = rect.y2
AND SEdge[borderTile] > rect.y1
THEN {
borderTile ← PtoR[borderTile.ws];
WHILE EEdge[borderTile] <= rect.x1
DO
borderTile ← PtoR[borderTile.ne]
ENDLOOP;
};
plane.current ← borderTile;
-- Progress Southwards along the west border.
WHILE SEdge[borderTile] > rect.y1
DO
borderTile ← EnumerateShadow[ borderTile];
borderTile ← PtoR[borderTile.ws];
WHILE EEdge[borderTile] <= rect.x1
DO
borderTile ← PtoR[borderTile.ne]
ENDLOOP
ENDLOOP;
-- borderTile is now the tile containing [rect.x1, rect.y1]. Progress Eastwards along the south border.
-- The second clause in the WHILE test is a little hack to ensure at least one tile is enumerated for degenerate (ie x1 = x2) rects. EnumerateShadow always enumerates one tile at least.
WHILE WEdge[borderTile] < rect.x2
OR WEdge[borderTile] <= rect.x1
DO
nextTile: REF Tile ← PtoR[borderTile.ne];
WHILE SEdge[nextTile] > rect.y1
DO
nextTile ← PtoR[nextTile.ws]
ENDLOOP;
borderTile ← EnumerateShadow[ borderTile];
borderTile ← nextTile
ENDLOOP;
IF perTile = NIL THEN data ← tileEnumeration;
RETURN [data]
};
TileAbove:
PROCEDURE [tile:
REF Tile, x:
INT]
RETURNS [
REF Tile] ~ {
-- Assumes x is within tile (i.e. WEdge[tile] <= x & EEdge[tile] > x)
IF ~ (WEdge[tile] <= x AND EEdge[tile] > x) THEN ERROR TilePosition;
tile ← tile.en;
WHILE WEdge[tile] > x
DO
tile ← tile.sw;
ENDLOOP;
RETURN [tile]
};
TileAt:
PUBLIC
PROCEDURE [plane:
REF Tesselation, pos: Position]
RETURNS [CornerStitching.TilePtr] ~ {
pos.x ← MIN[ INT.LAST.PRED, pos.x]; pos.y ← MIN[ INT.LAST.PRED, pos.y];
RETURN [plane.current ← FindTileContainingPoint[plane.current, [x~pos.x, y~pos.y]] ];
};
FindTileContainingPoint:
PROCEDURE [tile:
REF Tile, p: Coord]
RETURNS [
REF Tile] ~ {
WHILE tile.value = deleted DO
-- We may have be passed a tile ptr derived from the current tesselation access pointer in a Tesselation record. This tile may have been subject to a merge after the current tesselation access pointer was set to point at it, so we leap from deleted tile to deleted tile trying to reach stable ground. When a tile is merged the en REF is set to point at a valid tile, so we will always reach stable ground.
tile ← tile.en;
ENDLOOP;
IF tile.value = deleted THEN ERROR TileDeleted;
DO
-- Go North or South until a tile with appropriate range is found is found
SELECT NSRange[tile, p]
FROM
inside => NULL;
lesser =>
WHILE p.y < SEdge[tile]
DO
tile ← PtoR[tile.ws];
ENDLOOP;
greater =>
WHILE p.y >= NEdge[tile]
DO
tile ← tile.en;
ENDLOOP;
ENDCASE;
-- Go East or West until a tile with appropriate range is found is found
SELECT EWRange[tile, p]
FROM
inside => EXIT; -- We have found the tile with NSRange & EWRange satisfied
lesser =>
WHILE p.x < WEdge[tile]
DO
tile ← tile.sw;
ENDLOOP;
greater =>
WHILE p.x >= EEdge[tile]
DO
tile ← PtoR[tile.ne];
ENDLOOP;
ENDCASE;
-- Iterate to ensure going East-West did not screw up North-South
ENDLOOP;
RETURN [tile]
};
NSRange:
PROCEDURE [tile:
REF Tile, p: Coord]
RETURNS [RangeValue] ~ {
RETURN [
SELECT p.y
FROM
< SEdge[tile] => lesser,
>= NEdge[tile] => greater,
ENDCASE => inside]
};
EWRange:
PROCEDURE [tile:
REF Tile, p: Coord]
RETURNS [RangeValue] ~ {
RETURN [
SELECT p.x
FROM
< WEdge[tile] => lesser,
>= EEdge[tile] => greater,
ENDCASE => inside]
};
EWSplit:
PROCEDURE [plane: REF Tesselation, tile:
REF Tile, x:
INT]
RETURNS [ larger, smaller:
REF Tile] ~ {
-- The East one will be the new one.
eastTile: REF Tile;
t: REF Tile;
IF WEdge[tile] >= x OR EEdge[tile] <= x THEN ERROR TilePosition;
-- eastTile starts out a replica of tile
IF plane.deletionCache = NIL THEN eastTile ← NEW[Tile ← tile^]
ELSE {
eastTile ← plane.deletionCache;
plane.deletionCache ← plane.deletionCache.sw;
eastTile^ ← tile^
};
plane.tilesInTesselationCount ← plane.tilesInTesselationCount + 1;
-- Fix the tiles relative to each other.
tile.ne ← RtoP[eastTile];
eastTile.sw ← tile;
eastTile.pos.x ← x;
-- Fix North boundary
t ← eastTile.en;
WHILE t.pos.x >= x DO t.ws ← RtoP[eastTile]; t ← t.sw ENDLOOP;
tile.en ← t;
-- Fix East boundary
t ← PtoR[eastTile.ne];
WHILE t.sw = tile DO t.sw ← eastTile; t ← PtoR[t.ws] ENDLOOP;
-- Fix South boundary
t ← PtoR[tile.ws];
WHILE PtoR[t.ne].pos.x <= x DO t ← PtoR[t.ne] ENDLOOP;
eastTile.ws ← RtoP[t];
WHILE t.en = tile DO t.en ← eastTile; t ← PtoR[t.ne] ENDLOOP;
RETURN [larger~eastTile, smaller~tile]
};
NSSplit:
PROCEDURE [plane: REF Tesselation, tile:
REF Tile, y:
INT]
RETURNS [ larger, smaller:
REF Tile] ~ {
-- The North one will be the new one.
northTile: REF Tile;
t: REF Tile;
IF SEdge[tile] >= y OR NEdge[tile] <= y THEN ERROR TilePosition;
-- northTile starts out a replica of tile
IF plane.deletionCache = NIL THEN northTile ← NEW[Tile ← tile^]
ELSE {
northTile ← plane.deletionCache;
plane.deletionCache ← plane.deletionCache.sw;
northTile^ ← tile^
};
plane.tilesInTesselationCount ← plane.tilesInTesselationCount + 1;
-- Fix the tiles relative to each other.
tile.en ← northTile;
northTile.ws ← RtoP[tile];
northTile.pos.y ← y;
-- Fix East boundary
t ← PtoR[northTile.ne];
WHILE t.pos.y >= y DO t.sw ← northTile; t ← PtoR[t.ws] ENDLOOP;
tile.ne ← RtoP[t];
-- Fix North boundary
t ← northTile.en;
WHILE t.ws = RtoP[tile] DO t.ws ← RtoP[northTile]; t ← t.sw ENDLOOP;
-- Fix West boundary
t ← tile.sw;
WHILE t.en.pos.y <= y DO t ← t.en ENDLOOP;
northTile.sw ← t;
WHILE PtoR[t.ne] = tile DO t.ne ← RtoP[northTile]; t ← t.en ENDLOOP;
RETURN [larger~northTile, smaller~tile]
};
EWMerge:
PROCEDURE [plane: REF Tesselation, tileW, tileE:
REF Tile]
RETURNS [
REF Tile] ~ {
-- The East one will be deallocated.
IF tileE.sw # tileW OR PtoR[tileW.ne] # tileE OR tileW.pos.y # tileE.pos.y OR tileW.en.pos.y # tileE.en.pos.y THEN ERROR TilesNotAdjacent;
-- Fix the tiles relative to each other.
tileW.en ← tileE.en;
tileW.ne ← tileE.ne;
-- Fix North boundary
FOR t:
REF Tile ← tileW.en, t.sw
WHILE
PtoR[t.ws] = tileE
DO
t.ws ← RtoP[tileW];
ENDLOOP;
-- Fix East boundary
FOR t:
REF Tile ←
PtoR[tileW.ne],
PtoR[t.ws]
WHILE t.sw = tileE
DO
t.sw ← tileW;
ENDLOOP;
-- Fix South boundary
FOR t:
REF Tile ←
PtoR[tileE.ws],
PtoR[t.ne]
WHILE t.en = tileE
DO
t.en ← tileW;
ENDLOOP;
-- tileE may have been the current tesselation access pointer.
-- see corresponding comment in NSMerge.
tileE.value ← deleted;
tileE.en ← NIL;
IF plane.current = tileE THEN plane.current ← tileW;
tileE.sw ← plane.deletionCache;
plane.deletionCache ← tileE;
plane.tilesInTesselationCount ← plane.tilesInTesselationCount - 1;
RETURN [tileW];
};
NSMerge:
PROCEDURE [plane: REF Tesselation, tileS, tileN:
REF Tile]
RETURNS [
REF Tile] ~ {
-- The North one will be deallocated.
IF PtoR[tileN.ws] # tileS OR tileS.en # tileN OR tileS.pos.x # tileN.pos.x OR PtoR[tileS.ne].pos.x # PtoR[tileN.ne].pos.x THEN ERROR TilesNotAdjacent;
-- Fix the tiles relative to each other.
tileS.ne ← tileN.ne;
tileS.en ← tileN.en;
-- Fix East boundary
FOR t:
REF Tile ←
PtoR[tileS.ne],
PtoR[t.ws]
WHILE t.sw = tileN
DO
t.sw ← tileS;
ENDLOOP;
-- Fix North boundary
FOR t:
REF Tile ← tileS.en, t.sw
WHILE
PtoR[t.ws] = tileN
DO
t.ws ← RtoP[tileS];
ENDLOOP;
-- Fix West boundary
FOR t:
REF Tile ← tileN.sw, t.en
WHILE
PtoR[t.ne] = tileN
DO
t.ne ← RtoP[tileS];
ENDLOOP;
-- tileN may have been the current tesselation access pointer.
-- We explicitly check for this condition and correct it. OLD METHOD => It is given a special unique value to ensure that FindTileContainingPoint will never return this tile, however it is still stitched to tiles near to where it was so search effiency is not sacrificed. It is possible that after several subsequent megres tileN will point to other deleted tiles however there will be no cycles amongst the deleted tiles so that as soon as the current tesselation access pointer is set to point into valid tiles, the deleted tiles will be garbage collected.
tileN.value ← deleted;
tileN.en ← NIL;
IF plane.current = tileN THEN plane.current ← tileS;
tileN.sw ← plane.deletionCache;
plane.deletionCache ← tileN;
plane.tilesInTesselationCount ← plane.tilesInTesselationCount - 1;
RETURN [tileS];
};
PtoR:
PROCEDURE [p:
LONG
POINTER
TO Tile]
RETURNS [
REF Tile] ~
INLINE {
TRUSTED { RETURN [LOOPHOLE[p]] }
};
RtoP:
PROCEDURE [r:
REF Tile]
RETURNS [
LONG
POINTER
TO Tile] ~
INLINE {
TRUSTED { RETURN [LOOPHOLE[r]] }
};
Init:
PROCEDURE ~ {
InitTesselationBorderTiles[]
};
Init[];