-- COGVorLeePImpl.mesa: Lee/Preparata Point location algorithm
-- last modified by Stolfi October 18, 1982 6:48 pm
-- To do: Update for COGVoronoi, COGDrawing, etc
-- to run: run COGAll; run COGVoronoiImpl; run COGVorTestImpl; run COGVorIncrImpl; run COGVorLeepImpl
DIRECTORY
Rope USING [ROPE],
Real USING [LargestNumber],
IO USING [PutF, GetChar],
Process USING [Milliseconds, MsecToTicks, Pause],
GraphicsColor USING [green, red, yellow, black],
Graphics USING [DrawChar, SetColor, SetCP],
List USING [CompareProc, Sort, Length, IsAListOfRefAny],
COGDebug USING [out, in, Error],
COGHomo USING [Point, FinPt, InfPt, Precedes, Below, DistSq],
COGVoronoi USING
[Delaunay, DVertex, VVertex, Left, Right, Org, Dest, VertexNo, VertexPainter, EdgePainter],
COGDiagram USING [DEdge, Traverse, TopSort, VisitProc, EdgePredicate],
COGDrawing USING
[Drawing, GetProp, PutProp, Object, MenuProc, MouseProc, AddMouseAction,
AddMenuAction, Color, Size, Make, Add, PainterProc],
COGVorTest USING
[theDrawing, ShowVertex, ShowRegion, ShowDelay, CleanTheDrawing];
COGVorLeePImpl: CEDAR PROGRAM
IMPORTS
IO, COGHomo, Process, List, Graphics, COGDebug,
COGDiagram, COGDrawing, COGVoronoi, COGVorTest =
BEGIN
OPEN
Real, Rope, IO, GraphicsColor, Gr: Graphics, Homo: COGHomo, Diag: COGDiagram,
Draw: COGDrawing, COGVoronoi, COGVorTest, Bug: COGDebug;
-- LEFT-TO-RIGHT SORT OF VORONOI REGIONS - - - - - - - - - - - - - - - - - - - - - - - - -
SortRegions:
PROC [dlau: Delaunay, trace:
BOOL]
RETURNS [nFin:
NAT] =
TRUSTED
-- Sorts the finite vertices of a Delaunay diagram in such a way that, for any two adjacent points, the leftmost one occurs first. When this rule (and its transitive closure) are insufficient to decide the ordering of two points, the one with lowest y occurs first.
-- The final ordering is given by the 'no' field of each vertex (starting at dlau.minDV). Infinite data points will get the highest numbers (limDV-1, limDV-2, etc), .... The number of finite ones is returned in nFin.
BEGIN
nInf: NAT;
LROrdered: Diag.EdgePredicate = TRUSTED
{p: DVertex = Org[e];
q: DVertex = Dest[e];
RETURN [Homo.Precedes[p.pt, q.pt]]
};
Renumber: Diag.VisitProc = TRUSTED
{p: DVertex = Org[e];
IF Homo.FinPt[p.pt] THEN
{p.no ← dlau.minDV + nFin; nFin ← nFin+1}
ELSE
{nInf ← nInf+1; p.no ← dlau.limDV - nInf};
IF trace THEN ShowRegion [e, yellow, 5]
};
Bug.out.PutF ["\nVorLeeP.SortRegions: ENTER"];
dlau.limDV ← dlau.minDV + dlau.nDV;
nFin ← nInf ← 0;
[] ← Diag.TopSort[dlau.dg, Renumber, NIL, LROrdered, TRUE];
IF nFin + nInf # dlau.nDV THEN Bug.Error ["VorLeeP.SortRegions: Lost regions"];
Bug.out.PutF ["\nVorLeeP.SortRegions: EXIT"]
END;
-- CONSTRUCTION OF LEE-PREPARATA TRES - - - - - - - - - - - - - - - - - - - - - - - - - - -
-- The Lee-Preparata structure is a binary search tree, whose leaves are the Voronoi regions and whose internal nodes are monotone chains of Voronoi edges. Each chain is by itself a binary search tree: the nodes of the latter are of two kinds, called VTests and EdgeTests.
-- VTest nodes branch depending on whether the query point q is above or below the testY field. When an EdgeTest is encountered, q.y is known to be between those of the edge endpoints; that being the case, q.x can be tested against the edge.
-- A node of either kind has two pointers, which can refer to Voronoi regions (ultimate leaves), to its children in the same chain C, or to the roots of the chains that are children of C in the global tree. The latter is always the case for EdgeTests, and sometimes also for the VTests. The latter case occurs when the edge of C that covers q.y belongs also to some ancestor C' of C. At this point, we know on which side of C' the query point q lies, and therefore we do not have to test it again against that edge; we can proceed immediately to the root of the appropriate descendant chain C'' of C.
Tlee: TYPE = REF ANY; -- root of a Lee-Preparata tree: EdgeTest, VTest, or DVertex
VTest: TYPE = RECORD
[testV: VVertex,
onLeft, onRight: BOOL ← FALSE, -- DEBUG --
up, dn: Tlee -- to VTest or EdgeTest or DVertex
];
EdgeTest: TYPE = RECORD
[edge: Diag.DEdge,
left, right: Tlee -- to VTest or EdgeTest or DVertex
];
BuildLeePreparataTree:
PROC[dlau: Delaunay, trace:
BOOL]
RETURNS [tlee: Tlee] =
TRUSTED
BEGIN
-- Builds the Lee-Preparata tree structure for point location
-- Each chain is at first collected as list of edges; these are sorted by ascending y coordinate, and a chain tree is built from them. Since every edge is represented in only one chain (of highest possible level), there may be one or more gaps in the ranges of y-values covered by a given chain. A DummyEdge is temporarily inserted in each of those gaps (but not in the final structure).
DummyEdge:
TYPE =
RECORD
[topV: VVertex,
onLeft, onRight:
BOOL
-- TRUE if gap is in left/right ancestor of the chain
];
-- Once the Voronoi regions have been sorted from left to right, each edge can be assigned to a specific chain based on the ranks of the two regions on each side of it. If the regions are numbered in the range [0..n) (n>1), the root chain separates regions [0..k) and [k..n), where k = (0+n) DIV 2 is the chain index. Each group of regions is split in half by a child of the chain k, and so forth; in general, a group of regions [fst..lim) obtained in this process (lim>fst+1) is split into two subgroups [fst..k) and [k..lim) by the chain whose index is k = (fst+lim) DIV 2. The chain indices will lie therefore in the range (0..n).
-- Given two adjacent regions i and j, the edge between them will belong to a unique chain whose index is ChainIndex[i, j]
n: NAT; -- number of FINITE data points in the diagram
ChainIndex: TYPE = VertexNo;
ChainIx:
PROC [i, j: VertexNo]
RETURNS [ix: ChainIndex] =
TRUSTED
-- Returns the index of the monotone chain that separates region i from region j (i#j) and has highest possible level in the Lee-Preparata tree. Assumes the regions have been renumbered from 0 up.
BEGIN
fst: NAT ← 0;
lim: NAT ← n;
DO
-- assert: i and j IN [fst..lim)
ix ← (fst + lim)/2;
IF i < ix THEN
IF j < ix THEN
lim ← ix
ELSE
RETURN
ELSE
IF j < ix THEN
RETURN
ELSE
fst ← ix
ENDLOOP
END;
Descendants:
PROC [ix:
NAT]
RETURNS [fst, lim:
NAT] =
TRUSTED
-- Returns fst and lim such that LCA[i, j] = ix iff i IN [fst..ix) and j IN [ix..lim).
BEGIN
-- Returns fst and lim such that LCA[i, j] = ix iff i IN [fst..ix) and j IN [ix..lim).
av: NAT;
fst ← 0; lim ← n;
DO
-- Assert: ix IN [fst..lim)
av ← (fst + lim)/2;
IF ix < av THEN lim ← av
ELSE IF ix > av THEN fst ← av
ELSE RETURN
ENDLOOP
END;
-- The folowing records are temporary data structures.
ChainTable: TYPE = RECORD [elist: SEQUENCE nLists: NAT OF DEdgeList];
TipsTable: TYPE = RECORD [tip: SEQUENCE nDV: NAT OF VVertex];
RegionTable: TYPE = RECORD [region: SEQUENCE nDV: NAT OF Diag.DEdge];
MaxIxTable: TYPE = RECORD [maxix: SEQUENCE nVV: NAT OF ChainIndex];
DEdgeList: TYPE = LIST OF REF Diag.DEdge;
elists: REF ChainTable; -- edges in each Lee-Preparata chain
tips: REF TipsTable; -- upper Voronoi vertices of each region
regions: REF RegionTable; -- Delaunay vertices of each region
maxix: REF MaxIxTable; -- maximum chain index of edges inc. from below to each VVertex
SetUpTables:
PROC =
TRUSTED
-- Sorts the regions from left to right and sets the chain table in such a way that an edge between region i and region j is in elists[ChainIndex[i, j]].
-- Returns also in regions[k] n edge out of region k, and in tips[k] the its point with maximum y.
-- Finally, for every voronoi vertex v saves in maxchix[v.no] the maximum chain index of all edges for which it is the upper endpoint.
BEGIN
ThrowEdge: Diag.VisitProc =
TRUSTED
-- Throws all left-pointing edges out of q with finite endpoints into the appropriate chain bucket.
-- Also finds the higest Voronoi vertex tips[p.no], and sets regions[p.no] to an edge out of p, for each finite Delaunay vertex p.
{p: DVertex = Org[e];
q: DVertex = Dest[e];
IF p.no < n AND q.no < n THEN
{v: VVertex ← Left[e];
ix: ChainIndex ← ChainIx [MIN[p.no, q.no], MAX[p.no, q.no]];
regions[p.no] ← e;
IF tips[p.no] = NIL OR Homo.Below[tips[p.no].pt, v.pt] THEN
{tips[p.no] ← v};
maxix[v.no] ← MAX [maxix[v.no], ix];
IF p.no < q.no THEN
{elists[ix] ← CONS [NEW[Diag.DEdge ← e], elists[ix]]}
}
};
n ← SortRegions [dlau, TRUE];
elists ← NEW [ChainTable[n]];
tips ← NEW [TipsTable[n]];
regions ← NEW [RegionTable[n]];
maxix ← NEW [MaxIxTable [dlau.limVV]]; -- better they be numbered from 0 to nVV
[] ← Diag.Traverse [dlau.dg, ThrowEdge, NIL, all];
END;
CookYCoord:
PROC [p: Homo.Point]
RETURNS [y:
REAL] =
TRUSTED
BEGIN
-- Returns the cartesian y-coordinate of p, or plus/minus LasgestNumber (with correct sign) if p is infinite.
RETURN
[IF Homo.InfPt[p] THEN
IF p.y > 0.0 THEN LargestNumber
ELSE IF p.y < 0.0 THEN - LargestNumber
ELSE 0.0
ELSE p.y/p.w]
END;
SortChainEdges:
PROC [le: DEdgeList, fst, lim:
NAT, leftTip: VVertex]
RETURNS [sle:
LIST
OF
REF
ANY] =
TRUSTED
-- Sorts the edges of a chain by increasing Y, filling the gaps with dummy edges. The chain is assumed to bisect the regions [fst..lim); these parameters are used to figure out whether gaps lie on the left or right boundary of the current region set.
-- A Voronoi vertex lies on the left boundary iff it is adjacent to some region with sequence number lim or more.
-- The leftTip parameter gives the Voronoi vertex with maximum y on the regions at the left of the chain. It is used as a separator when the chain is empty.
BEGIN
OPEN List;
DecreasingY: CompareProc =
TRUSTED
-- Used for sorting the list by DECREASING y. The list is reversed later, while filling the gaps.
{xe: REF Diag.DEdge = NARROW [ref1];
ye: REF Diag.DEdge = NARROW [ref2];
xlv: Homo.Point = Left[xe^].pt;
ylv: Homo.Point = Left[ye^].pt;
RETURN
[IF Homo.Below[ylv, xlv] THEN less ELSE
IF Homo.Below[xlv, ylv] THEN greater ELSE equal]
};
OnLeftBoundary: PROC [v: VVertex] RETURNS [is: BOOL] = TRUSTED
{RETURN[maxix[v.no] < lim]};
tle: LIST OF REF ANY ← List.Sort [IsAListOfRefAny[le].list, DecreasingY];
t: LIST OF REF ANY;
e: Diag.DEdge;
de: REF DummyEdge;
lastV: VVertex ← NIL; -- Bottom vertex of chain processed so far (NIL at beginning)
eTop: VVertex;
sle ← NIL;
IF tle = NIL THEN
{-- no proper edges in chain - put a dummy edge from top to y separator
de ← NEW [DummyEdge ←
[topV: lastV,
onLeft: TRUE]];
sle ← CONS[de, sle];
lastV ← leftTip}
ELSE
{WHILE tle # NIL DO
t ← tle; e ← NARROW [t.first, REF Diag.DEdge]^; tle ← t.rest;
eTop ← Left[e];
IF Homo.Below [eTop.pt, IF lastV = NIL THEN [0.0, 1.0, 0.0] ELSE lastV.pt] THEN
{-- Found a gap
de ← NEW [DummyEdge ←
[topV: lastV,
onLeft: OnLeftBoundary[Left[e]]]];
sle ← CONS[de, sle];
lastV ← eTop};
t.rest ← sle; sle ← t;
lastV ← Right[e];
ENDLOOP};
-- Put last dummy edge if needed
IF Homo.Below [[0.0, -1.0, 0.0], lastV.pt] THEN
{de ← NEW [DummyEdge ←
[topV: lastV,
onLeft: OnLeftBoundary[lastV]]];
sle ← CONS[de, sle]}
END;
BuildPartialTreeFromChain:
PROC
[sle:
LIST
OF
REF
ANY, ne:
NAT, left, right: Tlee]
RETURNS
[root: Tlee, chTop: VVertex, rest:
LIST
OF
REF
ANY] =
TRUSTED
-- Builds a search tree for the first ne edges (including dummy ones) in the sorted list sle. The parameters left and right give the roots of the childern chains on either side.
-- Returns also the vertex chTop at the upper end of the partial chain, and the list rest of unused edges.
BEGIN
IF ne = 1 THEN
{WITH sle.first SELECT FROM
de: REF DummyEdge =>
{RETURN
[root: IF de.onLeft THEN right ELSE left,
chTop: de.topV,
rest: sle.rest]};
re: REF Diag.DEdge =>
{RETURN
[root: NEW [EdgeTest ← [edge: re^, left: left, right: right]],
chTop: Left[re^],
rest: sle.rest]};
ENDCASE => ERROR Bug.Error["UFO in chain."]}
ELSE
{up, dn: Tlee;
dnTop, upTop: VVertex;
vt: REF VTest;
[dn, dnTop, sle] ← BuildPartialTreeFromChain [sle, ne/2, left, right];
[up, upTop, sle] ← BuildPartialTreeFromChain [sle, ne - ne/2, left, right];
vt ← NEW [VTest ← [testV: dnTop, up: up, dn: dn]];
RETURN[vt, upTop, sle]}
END;
TreeData: TYPE = RECORD [root: Tlee, tip: VVertex];
BuildTreeOfChains:
PROC [fst, lim:
NAT]
RETURNS [rslt: TreeData] =
TRUSTED
BEGIN
-- Builds a Lee-Preparata tree for regions [fst..lim).
IF fst+1 = lim THEN
{RETURN [[Org[regions[fst]], tips[fst]]]}
ELSE
{rtix: NAT = (fst + lim)/2;
left: TreeData = BuildTreeOfChains [fst, rtix];
right: TreeData = BuildTreeOfChains [rtix, lim];
sle: LIST OF REF ANY = SortChainEdges [elists[rtix], fst, lim, left.tip];
ne: NAT = List.Length[sle];
rootChain: Tlee = BuildPartialTreeFromChain[sle, ne, left.root, right.root].root;
IF trace THEN
{CleanTheDrawing[repaint: TRUE];
FOR no: VertexNo IN [fst..lim) DO ShowRegion [regions[no], yellow, 2] ENDLOOP;
ShowChain [rootChain, left.root, right.root, green, 2]};
RETURN [[root: rootChain,
tip: IF Homo.Below[right.tip.pt, left.tip.pt] THEN left.tip ELSE right.tip]]}
END;
SetUpTables[];
RETURN [BuildTreeOfChains [0, n].root]
END;
-- POINT LOCATION USING THE LEE-PREPARATA TREE - - - - - - - - - - - - - - - - - - - - -
LeftOf:
PROC [qpt: Homo.Point, e: Diag.DEdge]
RETURNS [
BOOL] =
-- Returns TRUE iff qpt is to the left of the Voronoi edge corresponding to the Delaunay edge e, i.e. is closer to Org[e] than to Dest[e].
-- The edge e is assumed to point strictly to the left, and both its endpoints (and the point qpt) should be finite.
BEGIN
RETURN[Homo.DistSq[qpt, Org[e].pt] < Homo.DistSq[qpt, Dest[e].pt]]
END;
LocateInTlee:
PROC [qpt: Homo.Point, tlee: Tlee, trace:
BOOL]
RETURNS [dv: DVertex] =
TRUSTED
BEGIN
DO
WITH tlee SELECT FROM
dv: DVertex =>
{RETURN [dv]};
vt: REF VTest =>
{tlee ← IF Homo.Below[qpt, vt.testV.pt] THEN vt.dn ELSE vt.up}; -- what if equal???
et: REF EdgeTest =>
{tlee ← IF LeftOf[qpt, et.edge] THEN et.left ELSE et.right}
ENDCASE =>
{Bug.Error ["VorLeeP.LocateInTlee: invalid subtree"]}
ENDLOOP
END;
-- MENU AND MOUSE PROCEDURES - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
MenuSortRegions: Draw.MenuProc =
TRUSTED
BEGIN
dlob: Draw.Object = NARROW [Draw.GetProp [dr, $DelObj]];
dlau: Delaunay = NARROW[dlob.parms.data];
oldDelay: Process.Milliseconds = ShowDelay;
CleanTheDrawing [repaint: TRUE];
ShowDelay ← 500;
[] ← SortRegions[dlau, TRUE];
ShowDelay ← oldDelay;
Draw.PutProp[theDrawing, $ENum, NIL];
Draw.PutProp[theDrawing, $VNum, $TRUE]
END;
MenuBuildTlee: Draw.MenuProc =
TRUSTED
BEGIN
dlob: Draw.Object = NARROW [Draw.GetProp [dr, $DelObj]];
dlau: Delaunay = NARROW[dlob.parms.data];
tlee: Tlee;
CleanTheDrawing [repaint: TRUE];
tlee ← BuildLeePreparataTree [dlau, TRUE];
Draw.PutProp[dr, $Tlee, tlee]
END;
LocateMouse: Draw.MouseProc =
TRUSTED
BEGIN
tlee: Tlee = NARROW [Draw.GetProp [dr, $Tlee]];
dv: DVertex;
IF tlee = NIL THEN RETURN;
CleanTheDrawing[repaint: TRUE];
dv ← LocateInTlee [[x, y, 1.0], tlee, TRUE];
ShowVertex[dv, red, 3]
END;
-- PROCEDURES FOR DISPLAYING LEE-PREPARATA CHAINS - - - - - - - - - - - - - - - - - -
ChainObjData: TYPE = RECORD
[chainRoot, left, right: Tlee];
ChainPainter: Draw.PainterProc =
TRUSTED
BEGIN
cd: REF ChainObjData = NARROW[parms.data];
DoPaint: PROC [chain: Tlee] = TRUSTED
BEGIN
IF chain = cd.left OR chain = cd.right THEN RETURN;
WITH chain SELECT FROM
et: REF EdgeTest =>
{EdgePainter
[dr, context, sf,
[data: et.edge.rec, color: parms.color, size: parms.size, style: et.edge.ix]]};
vt: REF VTest =>
{VertexPainter [dr, context, sf, [data: vt.testV, color: parms.color, size: 2*parms.size]];
Gr.SetColor[context, black];
IF vt.up=cd.right OR vt.dn = cd.right THEN
{Gr.DrawChar[context, 'L]; Gr.SetCP [context, 8, 0, TRUE]}; -- DEBUG --
IF vt.up=cd.left OR vt.dn=cd.left THEN
{Gr.DrawChar[context, 'R]; Gr.SetCP [context, 8, 0, TRUE]};-- DEBUG --
DoPaint[vt.up]; DoPaint[vt.dn]}
ENDCASE => {};
END;
DoPaint[cd.chainRoot];
END;
ShowChain:
PROC
[chainRoot, left, right: Tlee, color: Draw.Color, width: Draw.Size] =
TRUSTED
-- Displays the root chain of the given Lee-Preparata tree. The left and right subchains of the tree should be given by the caller; if they are incorrect, spurious edges may be printed.
BEGIN
Draw.Add
[dr: theDrawing,
obj: Draw.Make
[ChainPainter,
[data: NEW[ChainObjData ← [chainRoot, left, right]], color: color, size: width]
],
order: 5];
Process.Pause[Process.MsecToTicks[2000]]
END;
-- MENU SETUP - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Bug.out.PutF ["\nVorLeeP: Hello! Say something:"];
[] ← Bug.in.GetChar[];
Draw.AddMenuAction[theDrawing, "Tlee", MenuBuildTlee, NIL, write];
Draw.AddMenuAction[theDrawing, "Sort", MenuSortRegions, NIL, write];
Draw.AddMouseAction [theDrawing, [button: red, kind: down, shift: TRUE], LocateMouse, NIL, write];
Bug.out.PutF ["\nVorLeeP: Enjoy yourself. Bye!"]
END.